jQuery-UI-秘籍-二-

29 阅读48分钟

jQuery UI 秘籍(二)

原文:zh.annas-archive.org/md5/6053054F727DA7F93DC0A95B33107695

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:添加对话框

在本章中,我们将介绍以下示例:

  • 对对话框组件应用效果

  • 等待 API 数据加载

  • 在对话标题中使用图标

  • 向对话框标题添加操作

  • 对对话框调整交互应用效果

  • 用于消息的模态对话框

介绍

对话框小部件为 UI 开发人员提供了一个工具,他们可以在不中断当前页面内容的情况下向用户呈现表单或其他信息片段;对话框创建了一个新的上下文。开箱即用,开发人员可以使用对话框选项做很多事情,并且其中许多功能默认情况下是打开的。这包括调整对话框的大小并在页面上移动它的能力。

在本章中,我们将解决在任何 Web 应用程序中典型对话框使用中的一些常见问题。通常需要调整对话框的控件和整体外观;我们将涉及其中一些。我们还将看看如何与 API 数据交互使对话框使用变得复杂以及处理方法。最后,我们可以通过查看如何以各种方式将效果应用于它们来为对话框小部件添加一些亮点。

对对话框组件应用效果

在开箱即用的情况下,对话框小部件允许开发人员在打开对话框时显示动画,以及在关闭时隐藏动画。此动画应用于整个对话框。因此,例如,如果我们指定show选项是fade动画,则整个对话框将对用户淡入视图。同样,如果hide选项是fade,则对话框会淡出视图,而不是立即消失。为了活跃这种showhide行为,我们可以对各个对话框组件进行操作。也就是说,我们可以将显示和隐藏效果应用于小部件内部的各个部分,如标题栏和按钮窗格,而不是将它们应用于整个对话框。

怎么做……

我们要创建的对话框在内容上非常简单。也就是说,在 HTML 中我们只会为对话框指定一些基本的title和内容字符串。

<div title="Dialog Title">
    <p>Basic dialog content</p>
</div>

为了将对话框组件的逐个动画化的想法变为现实,我们需要在几个地方扩展对话框小部件。特别是,我们将动画化小部件顶部的标题栏以及底部的按钮窗格。下面是 JavaScript 代码的样子:

(function( $, undefined ) {

$.widget( "ab.dialog", $.ui.dialog, {

    _create: function() {

        this._super();

        var dialog = this.uiDialog;

        dialog.find( ".ui-dialog-titlebar" ).hide();
        dialog.find( ".ui-dialog-buttonpane" ).hide();

    },

    open: function() {

        this._super();

        var dialog = this.uiDialog;

        dialog.find( ".ui-dialog-titlebar" ).toggle( "fold", 500 );
        dialog.find( ".ui-dialog-buttonpane" ).toggle( "fold", 500 );

    },

    close: function( event, isCallback ) {

        var self = this,
            dialog = this.uiDialog;

        if ( isCallback ) {
            this._super( event );
            return;
        }

        dialog.find( ".ui-dialog-titlebar" ).toggle( "fold", 500 );
        dialog.find( ".ui-dialog-buttonpane" ).toggle( "fold", 500, function(){
            self.element.dialog( "close", event, true );
        });

    }

});

})( jQuery );

$(function() {

    $( "div" ).dialog({
        show: "fade", 
        hide: "scale",
        buttons: {
            Cancel: function() {
                $( this ).dialog( "close" );
            }
        }
    });

});

当你打开页面时,你会看到独立于我们为对话指定的整体fade动画的各个对话框组件淡入视图。一旦可见,对话框应该看起来像这样:

怎么做……

你还会注意到,直到标题栏和按钮窗格应用fade效果之后,scale效果才会被应用。

它是如何工作的……

这段代码是规则的例外之一,我们没有提供关闭新扩展功能的机制。也就是说,我们在某些对话框方法的自定义实现中硬编码了更改,无法通过提供选项值来关闭。然而,这个例外是为了在复杂性和所需功能之间进行权衡。很可能这种自定义动画工作会作为特定项目需求的一部分进行,而不是对话框小部件功能的广泛扩展。

我们更改默认对话框实现的第一件事是在_create()方法中,我们隐藏了.ui-dialog-titlebar.ui-dialog-buttonpane组件。这是在调用_super()方法之后完成的,该方法负责创建基本对话框组件。即使对话框设置为使用autoOpen选项自动打开,_create()方法也不会实际显示它。因此,我们可以在用户没有注意到的情况下隐藏标题栏和按钮面板。

我们隐藏这两个组件的原因是因为我们希望在对话框打开时应用显示效果。下一个我们重写的方法open()就是这样做的。它首先调用_super()方法,该方法启动显示对话框的效果(在我们的情况下,我们告诉它在显示时淡入)。然后我们在标题栏和按钮面板上使用fold效果。

您会注意到我们在开始下一个动画之前不等待任何动画完成。对话框显示动画开始,然后是标题栏和按钮面板。这三个动画可能同时执行。我们之所以这样做是为了保持对话框的正确布局。我们要重写的最后一个方法是close()方法。这引入了一个有趣的解决方法,我们必须使用它来使得 _super() 在回调中起作用。即使在封闭范围内有 self 变量,我们在回调中调用 _super() 方法时也会遇到问题。因此,我们使用小部件元素,并假装我们是从小部件外部调用.dialog("close")一样。isCallback参数告诉close()方法调用 _super(),然后返回。我们之所以需要回调是因为我们实际上不想在完成按钮面板动画之前执行对话框隐藏动画。

等待 API 数据加载

通常情况下,对话框小部件需要从 API 加载数据。也就是说,并非所有对话框都由静态 HTML 构成。它们需要从 API 获取数据以使用 API 数据构建某些元素,例如select元素选项。

从 API 加载数据并构建结果元素并不是问题;我们一直在做这件事。挑战出现在我们尝试在对话上下文中执行这些活动时。我们不一定希望在从 API 加载数据并且用于显示对话框组件内部的 UI 组件已构建之前显示对话框,并且理想情况下,我们应该阻止对话框显示,直到对话框显示的组件准备好。

这在远程 API 功能中尤其棘手,因为不可能预测延迟问题。此外,对话框可能依赖于多个 API 调用,每个调用在对话框中填充自己的 UI 组件。

准备...

要为 API 数据问题实现解决方案,我们将需要一些基本的 HTML 和 CSS 来定义对话框及其内容。我们将在对话框中有两个空的 select 元素。这是 HTML 的样子:

<div id="dialog" title="Genres and Titles">
    <div class="dialog-field">
        <label for="genres">Genres:</label>
        <select id="genres"></select>
        <div class="ui-helper-clearfix"></div>
    </div>

    <div class="dialog-field">
        <label for="titles">Titles:</label>
        <select id="titles"></select>
        <div class="ui-helper-clearfix"></div>
    </div>
</div>

而且,这是上述代码的支持 CSS:

.dialog-field {
    margin: 5px;
}

.dialog-field label {
    font-weight: bold;
    font-size: 1.1em;
    float: left;
}

.dialog-field select {
    float: right;
}

如何做...

我们将给对话框小部件增加一个新选项来阻止在等待 API 请求时阻塞。此选项将允许我们传递一个延迟承诺的数组。承诺是用于跟踪单个 Ajax 调用状态的对象。通过一组承诺,我们能够使用简单的代码实现复杂的阻塞行为,如下所示:

(function( $, undefined ) {

$.widget( "ab.dialog", $.ui.dialog, {

    options: { 
        promises: []
    },

    open: function( isPromise ) {

        var $element = this.element,
            promises = this.options.promises;

        if ( promises.length > 0 && !isPromise ) {

            $.when.apply( $, promises ).then( function() {
                $element.dialog( "open", true );
            });

        }
        else {

            this._super();

        }

    },

});

})( jQuery );

$(function() {

    var repos = $.ajax({
        url: "https://api.github.com/repositories",
        dataType: "jsonp",
        success: function( resp ) {
            $.each( resp.data, function( i, v ) {
                $( "<option/>" ).html( v.name )
                                .appendTo( "#repos" );
            });
        },
    });

    var users = $.ajax({
        url: "https://api.github.com/users",
        dataType: "jsonp",
        success: function( resp ) {
            $.each( resp.data, function( i, v ) {
                $( "<option/>" ).html( v.login )
                                .appendTo( "#users" );
            });
        }
    });

    $( "#dialog" ).dialog({
        width: 400,
        promises: [
            repos.promise(),
            users.promise()
        ]
    });

});

一旦 API 数据返回,对于这两个调用,对话框将被显示,并且应该看起来像这样:

如何做...

它是如何工作的...

让我们首先看一下文档准备好的处理程序,在这里我们实际上是在实例化对话框小部件。这里定义的前两个变量 reposusers$.Deferred 对象。这代表了我们正在向 GitHub API 发送的两个 API 调用。这些调用的目的是分别填充 #repos#users select 元素。这些 select 元素构成了我们的 #dialog 内容的一部分。在每个 Ajax 调用中指定的 success 选项是一个回调,它执行创建 option 元素并将它们放置在 select 元素中的工作。

如果不自定义对话框小部件,这两个 API 调用将正常工作。对话框将打开,最终,选项将出现在 select 元素中(在对话框已经打开之后)。但是,您会注意到,我们正在向对话框传递一个 deferred.promise() 对象数组。这是我们赋予对话框小部件的新功能。延迟对象简单来说允许开发人员推迟某些可能需要一段时间才能完成的操作的后果,例如 Ajax 调用。承诺是我们从延迟对象中得到的,它允许我们组合一些条件,说出一个复杂的序列,例如进行多个 Ajax 调用,何时完成。

我们已添加到对话框小部件的自定义promises选项是在我们的open()方法的实现中使用的。在这里,我们可以利用这些承诺。基本上,我们正在将一个或多个承诺对象传递给对话框,一旦它们全部完成或解析为使用 jQuery 术语,我们就可以打开对话框。我们通过将承诺对象数组传递给$.when()函数来实现这一点,该函数在对话框上调用open()方法。但是,这里出现了一个我们必须处理的复杂情况。我们无法在回调函数内部调用_super(),因为核心小部件机制不知道如何找到父小部件类。

所以,我们必须假装我们是从小部件外部调用open()。我们通过使用self.element和额外的isPromise参数来做到这一点,指示我们自定义的open()实现如何行为。

在对话框标题中使用图标

对于某些对话框,根据应用程序的性质和对话框本身的内容,可能有益于在对话框标题旁边放置一个图标。这可能有利于用户提供额外的上下文。例如,编辑对话框可能具有铅笔图标,而用户个人资料对话框可能包含人物图标。

准备好了...

为了说明在对话框小部件的标题栏中添加图标,我们将使用以下内容作为我们的基本 HTML:

<div id="dialog" title="Edit">
    <div>
        <label>Field 1:</label>
        <input type="text"/>
    </div>
    <div>
        <label>Field 2:</label>
        <input type="text"/>
    </div>
</div>

如何操作...

我们需要定义的第一件事是一个自定义的 CSS 类,用于在将其放置在对话框标题栏中时正确对齐图标。CSS 如下所示:

.ui-dialog-icon {
    float: left;
    margin-right: 5px;
}

接下来,我们有我们的 JavaScript 代码来通过添加新的icon选项来自定义对话框小部件,以及使用我们的 HTML 作为源代码创建小部件的实例:

(function( $, undefined ) {

$.widget( "ab.dialog", $.ui.dialog, {

    options: {
        icon: false
    },

    _create: function() {

        this._super();

        if ( this.options.icon ) {

            var iconClass = "ui-dialog-icon ui-icon " + 
                            this.options.icon;

            this.uiDialog.find( ".ui-dialog-titlebar" )
                         .prepend( $( "<span/>" ).addClass( iconClass ));

        }

    },

});

})( jQuery );

$(function() {

    $( "#dialog" ).dialog({
        icon: "ui-icon-pencil",
        buttons: {
            Save: function() { $( this ).dialog( "close" ) }
        }
    });

});

打开时产生的对话框应该看起来像下面这样:

如何操作...

它是如何工作的...

对于这个特定的对话框实例,我们想显示铅笔图标。我们已添加到对话框小部件的icon选项允许开发人员从主题框架中指定图标类。在这种情况下,它是ui-icon-pencil。新的icon选项具有默认值false

我们正在覆盖_create()方法的默认对话框实现,以便我们可以在对话框标题栏中插入一个新的span元素,如果提供了icon选项。这个新的span元素得到了作为新选项值传递的图标类,以及ui-dialog-icon类,该类用于定位我们之前定义的图标。

将操作添加到对话框标题

默认情况下,对话框小部件为用户提供了一个不需要开发者干预的操作——标题栏中的关闭按钮。这是一个几乎适用于任何对话框的通用操作,因为用户期望能够关闭它们。此外,关闭对话框操作按钮是一个位于对话框右上角的图标,这并不是偶然的。这是一个标准的位置和动作,在图形窗口环境中以及其他操作中也是如此。让我们看看如何扩展放置在对话框小部件标题栏中的操作。

如何操作...

对于这个演示,我们只需要以下基本的对话框 HTML:

<div id="dialog" title="Dialog Title">
    <p>Basic dialog content</p>
</div>

接下来,我们将实现我们的对话框特化,添加一个新选项和一些创建使用该选项的新对话框实例的代码:

(function( $, undefined ) {

$.widget( "ab.dialog", $.ui.dialog, {

    options: {
        iconButtons: false
    },

    _create: function() {

        this._super();

        var $titlebar = this.uiDialog.find( ".ui-dialog-titlebar" );

        $.each( this.options.iconButtons, function( i, v ) {

            var button = $( "<button/>" ).text( v.text ),
                right = $titlebar.find( "[role='button']:last" )
                                 .css( "right" );

            button.button( { icons: { primary: v.icon }, text: false } )
                  .addClass( "ui-dialog-titlebar-close" )
                  .css( "right", (parseInt(right) + 22) + "px" )
                  .click( v.click )
                  .appendTo( $titlebar );

        });

    }

});

})( jQuery );

$(function() {

    $( "#dialog" ).dialog({
        iconButtons: [
            {
                text: "Search",
                icon: "ui-icon-search",
                click: function( e ) {
                    $( "#dialog" ).html( "<p>Searching...</p>" );
                }
            },
            {
                text: "Add",
                icon: "ui-icon-plusthick",
                click: function( e ) {
                    $( "#dialog" ).html( "<p>Adding...</p>" );
                }
            }
        ]
    });

});

当打开此对话框时,我们将在右上角看到我们传递给对话框的新操作按钮,如下截图所示:

如何操作...

它是如何工作的...

我们为对话框创建了一个名为iconButtons的新选项。这个新选项期望一个对象数组,其中每个对象都有与操作按钮相关的属性。像文本、图标类以及在用户打开对话框并单击按钮时执行的点击事件等。

在这个定制中,大部分工作都是在我们版本的_create()方法中进行的。在这里,我们遍历iconButtons选项中提供的每个按钮。在将新按钮插入标题栏时,我们首先创建button元素。我们还使用.ui-dialog-titlebar [role='button']:last选择器获取最后一个添加的操作按钮的宽度(这是需要计算操作按钮的水平位置的)。

接下来,我们按照按钮配置绑定click事件。对于我们添加的数组中的每个按钮,我们希望它放置在前一个按钮的左侧。因此,当我们首次开始遍历iconButtons数组时,默认的关闭操作是标题栏中的最后一个按钮。由于 CSS 结构需要一个固定的右值,我们必须计算它。为了做到这一点,我们需要列表中最后一个按钮的值。

将效果应用到对话框调整大小交互

默认情况下,对话框小部件允许用户通过拖动调整大小。实际的调整大小功能是由对话框在resizable选项为true时内部设置的resizable()交互小部件提供的。让我们看看如何访问内部可调整大小组件,以便我们可以使用animate特性。当设置在可调整大小组件上时,此选项会延迟重新绘制调整大小的组件,直到用户停止拖动调整大小手柄。

准备工作...

对于这个演示,我们只需要简单的对话框 HTML,如下所示:

<div id="dialog" title="Dialog Title">
    <p>Basic dialog content</p>
</div>

如何操作...

让我们为对话框小部件添加一个名为animateResize的新选项。当此选项为true时,我们将打开内部可调整大小交互小部件的animate选项。

(function( $, undefined ) {

$.widget( "ab.dialog", $.ui.dialog, {

    options: { 
        animateResize: false 
    },

    _makeResizable: function( handles ) {
        handles = (handles === undefined ? this.options.resizable : handles);
        var that = this,
            options = this.options,
            position = this.uiDialog.css( "position" ),
            resizeHandles = typeof handles === 'string' ?
                handles:
                "n,e,s,w,se,sw,ne,nw";

        function filteredUi( ui ) {
            return {
                originalPosition: ui.originalPosition,
                originalSize: ui.originalSize,
                position: ui.position,
                size: ui.size
            };
        }

        this.uiDialog.resizable({
            animate: this.options.animateResize,
            cancel: ".ui-dialog-content",
            containment: "document",
            alsoResize: this.element,
            maxWidth: options.maxWidth,
            maxHeight: options.maxHeight,
            minWidth: options.minWidth,
            minHeight: this._minHeight(),
            handles: resizeHandles,
            start: function( event, ui ) {
                $( this ).addClass( "ui-dialog-resizing" );
                that._trigger( "resizeStart", event, filteredUi( ui ) );
            },
            resize: function( event, ui ) {
                that._trigger( "resize", event, filteredUi( ui ) );
            },
            stop: function( event, ui ) {
                $( this ).removeClass( "ui-dialog-resizing" );
                options.height = $( this ).height();
                options.width = $( this ).width();
                that._trigger( "resizeStop", event, filteredUi( ui ) );
                if ( that.options.modal ) {
                    that.overlay.resize();
                }
             }
        })
        .css( "position", position )
        .find( ".ui-resizable-se" )
        .addClass( "ui-icon ui-icon-grip-diagonal-se" );
    }

});

})( jQuery );

$(function() {

    $( "#dialog" ).dialog({
        animateResize: true
    });

});

当创建并显示此对话框时,您将能够调整对话框的大小,观察到实际的调整现在是动画的。

它是如何工作的...

我们已经向对话框添加了animateResize选项,并为其提供了默认值false。要实际执行此功能,我们已经完全重写了对话框小部件在对话框创建时内部使用的_makeResizable()方法。事实上,我们已经采取了_makeResizable()的内部代码,并仅更改了一件事情——animate: this.options.animateResize

这有点多余,复制所有这些代码来打开一个简单的功能,比如动画化对话框调整交互。事实上,这不是理想的解决方案。一个更好的方法是调用_makeResizable()_super()版本,然后只需通过调用this.uiDialog.resizable( "option", "animate", true )打开动画即可。但在撰写本文时,这不符合预期。尽管我们的替代路径涉及多余的代码,但它只是展示了小部件工厂的灵活性。如果这种动画质量是用户界面的真实要求,我们很快就找到了一个可以忽略的折衷方案。

使用模态对话框进行消息传递

对话框小部件有一个保留的modal选项,用于当我们需要将用户的注意力集中在一件事上时。此选项显示对话框,同时防止用户与其余用户界面进行交互。他们别无选择,只能注意到。不言而喻,模态对话框应该节俭使用,特别是如果您想要用它来向用户广播消息。

让我们看看如何简化对话框以构建一个通用的通知工具在我们的应用程序中。本质上是一个模态对话框,用于那些我们不能让用户继续正在做的事情而不确保他们已经看到我们的消息的情况。

准备就绪...

这是这个示例所需的 HTML 看起来像。请注意,#notify div,它将成为一个对话框小部件,没有内容,因为我们的新通知小部件将提供一些内容。

<div id="notify"></div>

<button id="show-info">Show Info</button>
<button id="show-error">Show Error</button>

如何做...

让我们继续定义一个新的通知小部件,能够向用户显示错误和信息消息,就像这样:

(function( $, undefined ) {

$.widget( "ab.notify", $.ui.dialog, {

    options: { 
        modal: true,
        resizable: false,
        draggable: false,
        minHeight: 100,
        autoOpen: false,
        error: false
    },

    open: function() {

        var error = this.options.error,
            newClass = error ? "ui-state-error" : 
                               "ui-state-highlight",
            oldClass = error ? "ui-state-highlight" :
                               "ui-state-error";

        this.element.html( this.options.text );

        this.uiDialog.addClass( newClass )
                     .removeClass( oldClass )
                     .find( ".ui-dialog-titlebar" )
                     .removeClass( "ui-widget-header ui-corner-all" );

        this._super();

    },

});

})( jQuery );

$(function() {

    $( "#notify" ).notify();

    $( "#show-info, #show-error" ).button();

    $( "#show-info" ).click( function( e ) {

        $( "#notify" ).notify( "option", {
            error: false,
            text: "Successfully completed task"
        });

        $( "#notify" ).notify( "open" );

    });

    $( "#show-error" ).click(function( e ) {

        $( "#notify" ).notify( "option", {
            error: true,
            text: "Failed to complete task"
        });

        $( "#notify" ).notify( "open" );

    })

我们在这里创建的两个按钮用于演示通知小部件的功能。如果您点击#show-info按钮,您将看到以下信息消息:

如何做...

如果您点击#show-error按钮,您将看到此错误消息:

如何做...

它是如何工作的...

我们刚刚创建的notify小部件继承了对话框小部件的所有功能。在我们的小部件中,我们首先定义的是可用选项。在这种情况下,我们扩展了对话框小部件的options对象,并添加了一些新选项。您还会注意到,我们提供了一些更新后的对话框选项的默认值,例如打开modal并关闭draggable。每个 notify 实例都将共享这些默认值,因此没必要每次都要定义它们。

open()方法属于对话框小部件,我们在这里进行了重写,以实现将通知消息的文本插入对话框内容的自定义功能。我们还根据error选项设置对话框的状态。如果这是一个错误消息,我们将整个对话框应用ui-state-error类。如果error选项为false,我们应用ui-state-highlight类。最后,对话框标题栏组件被简化,删除了一些类,因为我们在消息显示中没有使用它。

在应用程序代码中,我们首先创建的是 notify 小部件的实例。然后我们创建了演示按钮,并将click事件绑定到将显示错误消息或信息性消息的功能,具体取决于点击了哪个按钮。

第六章:制作菜单

在本章中,我们将涵盖:

  • 创建可排序的菜单项

  • 高亮显示活动菜单项

  • 使用效果与菜单导航

  • 动态构建菜单

  • 控制子菜单的位置

  • 对子菜单应用主题

介绍

jQuery UI 菜单小部件接受链接列表,并通过处理子菜单中的导航,以及应用主题框架中的类,将它们呈现为一个连贯的菜单。我们可以使用默认提供的选项来定制菜单到一定程度。在其他情况下,例如当我们希望菜单项可排序时,我们可以轻松地扩展小部件。

创建可排序的菜单项

默认情况下,菜单小部件保留用于创建菜单项的列出元素的顺序。这意味着如果在菜单小部件中使用的 HTML 的创建者更改了排序方式,这将反映在渲染的菜单中。这对开发人员来说很好,因为它让我们控制如何向用户呈现项目。但是,也许用户对菜单项的排序有更好的想法。

通过将菜单小部件与sortable 交互小部件相结合,我们可以为用户提供这种灵活性。然而,有了这种新的能力,我们将不得不解决另一个问题;保留用户选择的顺序。如果他们可以按自己的意愿安排菜单项,那就太好了,但是如果他们每次加载页面都必须重复相同的过程,那就不太好了。因此,我们还将看看如何在 cookie 中保存排序后的菜单顺序。

准备工作

让我们使用以下 HTML 代码为我们的菜单小部件。这将创建一个具有四个项目的菜单,所有项目都在同一级别:

<ul id="menu">
    <li id="first"><a href="#">First Item</a></li>
    <li id="second"><a href="#">Second Item</a></li>
    <li id="third"><a href="#">Third Item</a></li>
    <li id="fourth"><a href="#">Fourth Item</a></li>
</ul>

如何做到…

现在让我们看一下用于扩展菜单小部件以提供可排序行为的 JavaScript。

(function( $, undefined ) {

$.widget( "ab.menu", $.ui.menu, {

    options: {
        sortable: false
    },

    _create: function() {

        this._super();

        if ( !this.options.sortable ) {
            return;
        }

        var $element = this.element,
            storedOrder = $.cookie( $element.attr( "id" ) ),
            $items = $element.find( ".ui-menu-item" );
        if ( storedOrder ) {

            storedOrder = storedOrder.split( "," );

            $items = $items.sort( function( a, b ) {

                var a_id = $( a ).attr( "id" ),
                    b_id = $( b ).attr( "id" ),
                    a_index = storedOrder.indexOf( a_id ),
                    b_index = storedOrder.indexOf( b_id );

                return a_index > b_index;

            });

            $items.appendTo( $element );

        }

        $element.sortable({

            update: function( e, ui ) {

                var id = $( this ).attr( "id" ),
                    sortedOrder = $( this ).sortable( "toArray" )
                                           .toString();

                $.cookie( id, sortedOrder );

            }

        });

    },

});

})( jQuery );

$(function() {
    $( "#menu" ).menu( { sortable: true } );
});

如果您在浏览器中查看此菜单,您会注意到您可以将菜单项拖到任何您喜欢的顺序中。此外,如果您刷新页面,您会看到顺序已经被保留了。

如何做到…

工作原理...

在本示例中创建的菜单实例被赋予了一个sortable选项值为true。这是我们添加到菜单小部件的新选项。我们大部分的扩展工作是在我们自己的_create()方法的重新呈现中执行的。我们在这里要做的第一件事是调用方法的原始实现,因为我们希望菜单像往常一样创建;我们通过使用_super()方法来做到这一点。从这里开始,我们将保持菜单项的排序顺序。

如果sortable选项的评估结果不为true,我们将退出,没有任何事情可做。如果此选项为true,且我们需要对菜单项目进行排序,我们尝试加载一个 Cookie,使用此菜单的 ID。此 Cookie 的值存储在一个名为storedOrder的变量中,因为它恰好代表了存储的用户排序。如果用户已经对菜单进行了排序,我们将菜单项目的顺序存储在 Cookie 中。例如,Cookie 值可能类似于second,fourth,first,third。这些是菜单项目的 ID。在我们分割逗号分隔列表时,我们得到了一个以正确顺序排列的菜单项目数组。

最后,我们必须将可排序交互小部件应用于菜单。我们将可排序配置传递给在更新排序顺序时使用的函数。使用可排序小部件的toArray()方法序列化菜单项目的排序顺序,并在此处使用菜单 ID 更新 Cookie 值。

关于此示例中使用 Cookie 有两件事情需要注意。首先,我们使用了 Cookie jQuery 插件。此插件体积小,在互联网上广泛使用。然而,值得一提的是,该插件不随 jQuery 或 jQuery UI 一起发布,您的项目将需要管理此依赖项。

第二个需要注意的事情是关于本地主机域。在所有浏览器中,Cookie 存储功能在本地将无法正常工作。换句话说,通过网络服务器查看时会正常工作。如果您真的需要在 Google Chrome 浏览器中测试此代码,您可以像我一样使用 Python 绕过它。在操作系统控制台中,运行以下代码:

python -m SimpleHTTPServer

高亮显示活动菜单项目

对于菜单小部件,根据项目的配置方式,唯一能判断项目是否激活的方法是页面 URL 由于点击项目而改变。菜单项目不会明显地指示任何实际发生的事情。例如,菜单中的项目一旦被点击,可能会改变可视状态。如果开发人员在用户界面中使用菜单小部件作为导航工具,这将特别有帮助。让我们看看如何扩展菜单小部件的功能,以便使用主题框架的部分提供此功能。

准备就绪

我们将在这里使用以下 HTML 代码作为我们的菜单示例。请注意,此特定菜单具有嵌套子菜单:

<ul id="menu">
    <li><a href="#first">First Item</a></li>
    <li><a href="#second">Second Item</a></li>
    <li><a href="#third">Third Item</a></li>
    <li>
      <a href="#forth">Fourth Item</a>
      <ul>
        <li><a href="#fifth">Fifth</a></li>
        <li><a href="#sixth">Sixth</a></li>
      </ul>
    </li
</ul>

如何做...

为了突出显示活动菜单项目,我们将需要通过一些额外规则扩展主题框架。

.ui-menu .ui-menu-item {
    margin: 1px 0;
    border: 1px solid transparent;
}

.ui-menu .ui-menu-item a.ui-state-highlight { 
    font-weight: normal; 
    margin: -px; 
}

接下来,我们将通过新的highlight选项和必要的功能扩展菜单小部件本身。

(function( $, undefined ) {

$.widget( "ab.menu", $.ui.menu, {

    options: {
      highlight: false
    },

    _create: function() {

      this._super();

        if ( !this.options.highlight ) {
          return;
        }

        this._on({
          "click .ui-menu-item:has(a)": "_click"
        });

    },

    _click: function( e ) {

      this.element.find( ".ui-menu-item a" )
        .removeClass( "ui-state-highlight" );

        $( e.target ).closest( ".ui-menu-item a" )
          .addClass( "ui-state-highlight ui-corner-all" );

    }

});

})( jQuery );

$(function() {
    $( "#menu" ).menu( { highlight: true });
});

如果您查看此菜单,您会注意到一旦选择了一个菜单项目,它会保持高亮状态。

如何做...

工作原理...

我们在这里定义的 CSS 规则是为了使 ui-state-highlight 类在应用于菜单项时能够正常运行。首先,使用 .ui-menu .ui-menu-item 选择器,我们将 margin 设置为在应用 ui-state-highlight 类后适当对齐菜单项的内容。我们还给每个菜单项一个不可见的 border,以防止鼠标进入和鼠标离开事件将菜单项挤出位置。接下来的选择器,.ui-menu .ui-menu-item a.ui-state-highlight,适用于我们将 ui-state-highlight 类应用于菜单项后。这些规则还控制了定位,并防止菜单失去对齐。

切换到 JavaScript 代码,您可以看到我们为菜单部件提供了一个新的 highlight 选项。在我们自定义的 _create() 方法中,我们调用相同方法的原始实现,然后再添加我们的事件处理程序。由 jQuery UI 基础部件定义的 _on() 方法在这里用于将我们的事件处理程序绑定到 click .ui-menu-item:has(a) 事件;这个事件在 menu 部件内部也使用。在这个处理程序内部,我们从任何已经应用 ui-state-highlight 类的菜单项中删除它。最后,我们将 ui-state-highlight 类添加到刚刚点击的菜单项上,还添加了 ui-corner-all 类,该类通过主题属性定义了圆角元素。

使用菜单导航效果

在应用效果到菜单部件时,我们可以采取几种方法。我们在菜单部件中哪些地方可以应用效果?用户将鼠标指针悬停在菜单项上,这会导致状态更改。用户展开子菜单。这两个动作是我们可以通过一些动画来提升视觉效果的主要交互。让我们看看如何使用尽可能少的 JavaScript 来解决这些效果,而不是使用 CSS 过渡。过渡是一个新兴的 CSS 标准,迄今为止,并非所有浏览器都支持它们使用标准语法。然而,按照渐进增强的思路,以这种方式应用 CSS 意味着即使在不支持它的浏览器中,基本的菜单功能也会正常工作。我们可以避免编写大量 JavaScript 来对菜单导航进行动画处理。

准备工作

对于这个示例,我们可以使用任何标准的菜单 HTML 代码。理想情况下,它应该有一个子菜单,这样我们就可以观察到它们展开时应用的过渡效果。

如何做...

首先,让我们定义所需的 CSS 过渡,以便在菜单项和子菜单在状态更改时应用。

.ui-menu-animated > li > ul {
    left: 0;
    transition: left 0.7s ease-out;
    -moz-transition: left .7s ease-out;
    -webkit-transition: left 0.7s ease-out;
    -o-transition: left 0.7s east-out;
}

.ui-menu-animated .ui-menu-item a {
    border-color: transparent;
    transition: font-weight 0.3s,
      color 0.3s,
      background 0.3s,
      border-color 0.5s;
    -moz-transition: font-weight 0.3s,
       color 0.3s,
       background 0.3s,
       border-color 0.5s;
    -webkit-transition: font-weight 0.3s,
       color 0.3s,
       background 0.3s,
       border-color 0.5s;
    -o-transition: font-weight 0.3s,
       color 0.3s,
       background 0.3s,
       border-olor 0.5s;
}

接下来,我们将介绍对菜单部件本身的一些修改,以控制任何给定菜单实例的动画功能。

(function( $, undefined ) {

$.widget( "ab.menu", $.ui.menu, {

    options: {
        animate: false
    },

    _create: function() {

        this._super();

        if ( !this.options.animate ) {
            return;
        }

        this.element.find( ".ui-menu" )
                     .addBack()
                     .addClass( "ui-menu-animated" );

    },

  _close: function( startMenu ) {

        this._super( startMenu );

        if ( !this.options.animate ) {
            return;
        }

        if ( !startMenu ) {
            startMenu = this.active ? this.active.parent() : this.element;
        }

        startMenu.find( ".ui-menu" ).css( "left", "" );

          }

});

})( jQuery );

$(function() {
    $( "#menu" ).menu( { animate: true } );
});

现在,如果你在浏览器中查看这个菜单并开始与它交互,你会注意到应用悬停状态时的平滑过渡。你也会注意到,展开子菜单时,应用的过渡似乎将它们向右滑动。

它是如何工作的...

首先,让我们考虑一下定义了我们所看到应用到menu部件的过渡的 CSS 规则。.ui-menu-animated > li > ul选择器将过渡应用到子菜单上。声明的第一个属性left: 0只是一个初始化程序,允许某些浏览器更好地与过渡配合。接下来的四行定义了左属性的过渡。菜单部件在展开子菜单时,使用的是位置实用程序部件,它在子菜单上设置了leftCSS 属性。我们在这里定义的过渡将在0.7秒的时间跨度内对left属性进行更改,并且会在过渡结束时减缓。

我们有多个过渡定义的原因是一些浏览器支持它们自己的供应商前缀版本的规则。因此,我们从通用版本开始,然后是特定于浏览器的版本。这是一个常见的做法,当浏览器特定的规则变得多余时,我们可以将其删除。

接下来是.ui-menu-animated .ui-menu-item a选择器,适用于每个菜单项。你可以看到这里的过渡涉及几个属性。在这个过渡中,每个属性都是ui-state-hover的一部分,我们希望它们被动画化。由于我们的调整,border-color过渡的持续时间稍长。

现在让我们看看将这个 CSS 运用到 JavaScript 的方法。我们通过添加一个新的animate选项来扩展菜单部件,该选项将上述定义的过渡应用到部件上。在我们的_create()方法版本中,我们调用了原始的_create()实现,然后将ui-menu-animated类应用到主ul元素和任何子菜单上。

延伸_close()方法的原因只有一个。这是在关闭子菜单时调用的。然而,当首次显示子菜单时,left CSS 属性是由position实用程序计算的。下一次显示时,它不必计算left属性。这是一个问题,因为很明显,如果我们尝试对left属性值进行动画更改,这会成为显而易见的问题。因此,我们只需要在关闭菜单时将left属性设置回0

动态构建菜单

经常情况下,菜单在与用户交互时会发生变化。换句话说,我们可能需要在菜单实例化后扩展菜单的结构。或者在构建最终成为菜单部件的 HTML 时,可能并没有所有必要的信息可用;例如,菜单数据可能只以JavaScript 对象表示法JSON)格式可用。让我们看看如何动态构建菜单。

准备

我们将从以下基本菜单 HTML 结构开始。我们的 JavaScript 代码将扩展这个结构。

<ul id="menu">
    <li><a href="#">First Item</a></li>
    <li><a href="#">Second Item</a></li>
    <li><a href="#">Third Item</a></li>
</ul>

如何做...

让我们创建菜单小部件,然后我们将扩展菜单 DOM 结构。

$(function() {

    var $menu = $( "#menu" ).menu(),
        $submenu = $( "<li><ul></ul></li>" ).appendTo( $menu );

    $submenu.prepend( $( "<a/>" ).attr( "href", "#" )
                                 .text( "Fourth Item" ) );

    $submenu.find( "ul" ).append( 
$( "<li><a href='#'>Fifth Item</a>" ) )
                                      .append( $( "<li><a href='#'>Sixth Item</a>" ) );

    $menu.menu( "refresh" );

});

当您查看这个菜单时,不再只有我们最初的三个项目,而是现在呈现了我们刚刚添加的三个新项目。

如何做...

工作原理是什么...

如果我们在 JavaScript 代码中不断添加新的菜单项,我们只会看到最初的三个项目。但是,我们正在使用核心 jQuery DOM 操纵工具来构建和插入一个子菜单。之后,我们必须调用 refresh() 菜单方法,它会为新的菜单项添加适当的 CSS 类和事件处理程序。例如,如果我们将 DOM 插入代码移到 menu 小部件被实例化之前,则没有理由调用 refresh(),因为菜单构造函数会直接调用它。

还有更多...

上述方法在菜单中插入新项目确实有其缺点。一个明显的缺点是实际构建新菜单项和子菜单的 DOM 插入代码不易维护。我们的示例已经将结构硬编码了,而大多数应用程序通常不这样做。相反,我们通常至少有一个数据源,可能来自 API。如果我们可以传递给菜单小部件一个标准格式的数据源,那就太好了。菜单小部件将负责我们上面实现的底层细节。

让我们尝试修改代码,以便更多的责任移到菜单小部件本身。我们将以与上面的代码完全相同的结果为目标,但我们将通过扩展菜单小部件,并传入代表菜单结构的数据对象来实现。我们将使用完全相同的 HTML 结构。以下是新的 JavaScript 代码:

(function( $, undefined ) {

$.widget( "ab.menu", $.ui.menu, {

    options: {
        data: false
    },

    _create: function() {

        this._super();

        if ( !this.options.data ) {
            return;
        }

        var self = this;

        $.each( this.options.data, function( i, v ) {
            self._insertItem( v, self.element );
        });

        this.refresh();

    },

    _insertItem: function( item, parent ) {

        var $li = $( "<li/>" ).appendTo( parent );

        $( "<a/>" ).attr( "id", item.id )
                   .attr( "href", item.href )
                   .text( item.text )
                   .appendTo( $li );

        if ( item.data ) {

            var $ul = $( "<ul/>" ).appendTo( $li ),
                self = this;

            $.each( item.data, function( i, v ) {
                self._insertItem( v, $ul );
            });

        }

    }

});

})( jQuery );

$(function() {

    $( "#menu" ).menu({
        data: [
            {
                id: "fourth",
                href: "#",
                text: "Fourth Item"
            },
            {
                id: "fifth",
                href: "#",
                text: "Fifth Item",
                data: [
                    {
                        id: "sixth",
                        href: "#",
                        text: "Sixth Item"
                    },
                    {
                        id: "seventh",
                        href: "#",
                        text: "Seventh Item"
                    }
                ]
            }
        ]
    });

});

如果您运行这段修改后的代码,您会发现结果与我们上面编写的原始代码没有任何变化。这种改进纯粹是一种重构,将难以维护的代码变成了更长寿的东西。

我们在这里引入的新选项 data 期望一个菜单项数组。该项是一个带有以下属性的对象:

  • id:它是菜单项的 id

  • href:它是菜单项链接的 href

  • text:它是项目标签的项

  • data:它是一个嵌套的子菜单

最后一个选项只是表示子菜单的菜单项嵌套数组。我们对 _create() 方法的修改将遍历数据选项数组(如果提供),并在每个对象上调用 _insertItem()_insertItem() 方法是我们引入的新东西,并不会覆盖任何现有的菜单功能。在这里,我们正在为传入的菜单数据创建必要的 DOM 元素。如果这个对象有一个嵌套的数据数组,也就是子菜单,那么我们会创建一个 ul 元素,并递归调用 _inserItem(),将 ul 作为父元素传递进去。

我们传递给菜单的数据比以前的版本更易读和可维护。 例如,现在传递 API 数据所需的工作相对较少。

控制子菜单的位置

菜单小部件使用位置小部件来控制任何子菜单在可见时的目的地。 默认情况下,将子菜单的左上角放置在展开子菜单的菜单项的右侧。 但是,根据我们的菜单大小、子菜单的深度和 UI 中围绕大小的其他约束,我们可能希望使用不同的默认值来设置子菜单的位置。

准备工作

我们将使用以下 HTML 结构来进行子菜单定位演示:

<ul id="menu">
            <li><a href="#first">First Item</a></li>
            <li><a href="#second">Second Item</a></li>
            <li><a href="#third">Third Item</a></li>
            <li>
              <a href="#forth">Fourth Item</a>
              <ul>
                <li><a href="#fifth">Fifth</a></li>
                <li>
                  <a href="#sixth">Sixth</a>
                  <ul>
                    <li><a href="#">Seventh</a></li>
                    <li><a href="#">Eighth</a></li>
                    </ul>
                  </li>
                </ul>
            </li>
        </ul

如何做...

当我们实例化此菜单时,我们将传递一个position选项,如下所示:

<ul id="menu">
            <li><a href="#first">First Item</a></li>
            <li><a href="#second">Second Item</a></li>
            <li><a href="#third">Third Item</a></li>
            <li>
                <a href="#forth">Fourth Item</a>
                <ul>
                    <li><a href="#fifth">Fifth</a></li>
                    <li>
                        <a href="#sixth">Sixth</a>
                        <ul>
                            <li><a href="#">Seventh</a></li>
                            <li><a href="#">Eighth</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>

当所有子菜单展开时,我们的菜单将与下图所示类似:

如何做...

如何运作...

在前面的示例中,我们向菜单小部件传递的position选项与我们直接传递给位置小部件的选项相同。 位置小部件期望的of选项是活动菜单项或子菜单的父项。 所有这些选项都传递给_open()方法中的位置小部件,该方法负责展开子菜单。

将主题应用于子菜单

当菜单小部件显示子菜单时,外观上没有明显的区别。 也就是说,在视觉上,它们看起来就像是主菜单。 我们希望向用户展示主菜单和其子菜单之间的一点对比;我们可以通过扩展小部件以允许将自定义类应用于子菜单来实现这一点。

准备工作

让我们使用以下标记来创建带有几个子菜单的菜单小部件:

<ul id="menu">
            <li><a href="#">First Item</a></li>
            <li><a href="#">Second Item</a></li>
            <li><a href="#">Third Item</a></li>
            <li>
                <a href="#">Fourth Item</a>
                <ul>
                    <li><a href="#">Fifth</a></li>
                    <li>
                        <a href="#">Sixth</a>
                        <ul>
                            <li><a href="#">Seventh</a></li>
                            <li><a href="#">Eighth</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>

如何做...

我们将通过添加一个新的submenuClass选项并将该类应用于子菜单来扩展菜单小部件,如下所示:

(function( $, undefined ) {

$.widget( "ab.menu", $.ui.menu, {

    options: {
      submenuClass: false
    },

    refresh: function() {

      if ( this.options.submenuClass ) {

        this.element.find( this.options.menus + ":not(.ui-menu)" )
          .addClass( this.options.submenuClass );

        }

        this._super();

    }

});

})( jQuery );

$(function() {
    $( "#menu" ).menu( { submenuClass: "ui-state-highlight } );
});

下面是子菜单的外观:

如何做...

如何运作...

在这里,我们使用一个新的submenuClass选项扩展了菜单小部件。 我们的想法是,如果提供了这个类,我们只想将它应用于小部件的子菜单。 我们通过重写refresh()菜单方法来实现这一点。 我们查找所有子菜单并将submenuClass应用于它们。 您会注意到,在调用原始实现此方法的_super()方法之前,我们应用了这个类。 这是因为我们正在寻找尚未具有ui-menu类的菜单。 这些是我们的子菜单。

第七章:进度条

在本章中,我们将涵盖以下主题:

  • 显示文件上传进度

  • 动画化进度变化

  • 创建进度指示器小部件

  • 使用状态警告阈值

  • 给进度条添加标签

介绍

progressbar 小部件相当简单——因为它没有太多的移动部分。事实上,它只有一个移动部分,即值栏。但是简单并不意味着进度条比其他小部件功能更弱。我们将看看如何在本章中利用这种简单性。进度条可以表达从文件上传进度到服务器端进程再到容量利用率的任何内容。

显示文件上传进度

如果有一种简单直接的方法可以使用进度条小部件显示文件上传的进度就好了。不幸的是,我们没有这样的奢侈。文件的上传发生在页面转换之间。然而,使用进度条小部件显示上传进度所需的必要技巧,由于现代标准和浏览器的发展,已经变得更加简洁。让我们看看如何利用Ajax请求中 XML HTTP 请求对象的 onprogress 事件。

准备工作

为了演示,我们将创建一个带有简单文件字段的简单表单。在表单内部,我们将创建一些用于显示进度条小部件的 HTML。它将在用户启动文件上传之前被隐藏。

<form action="http://127.0.0.1:8000/" method="POST">
    <input type="file" name="fileupload"/>
    <br/>
    <input type="submit" value="Upload"/>
    <div id="upload-container" class="ui-helper-hidden">
        <strong id="upload-value">Uploading...</strong>
        <div id="upload-progress"></div>
    </div>
</form>

操作方法...

更新文件上传过程中更新进度条小部件所需的大部分工作实际上是在 Ajax 请求机制和 onprogress 事件处理程序中完成的。以下代码很好地说明了为什么小部件设计者应该以简单为目标。生成的小部件适用于各种情境。

$(function() {

    $( "#upload-progress" ).progressbar();

    $( "form" ).submit( function( e ) {

        e.preventDefault();

        $.ajax({
            url: $( this ).attr("action"),
            type: "POST",
            data: new FormData( this ), 
            cache: false,
            contentType: false,
            processData: false,
            xhr: function() {

                xhr = $.ajaxSettings.xhr();

                if ( xhr.upload ) {
                    xhr.upload.onprogress = onprogress;
                }

                return xhr;

            }

        });

        return false;

    });

    var onprogress = function( e ) {

        var uploadPercent = ( e.loaded / e.total * 100 ).toFixed();

        $( "#upload-container" ).show();
        $( "#upload-value" ).text( "Uploading..." + uploadPercent + "%" );
        $( "#upload-progress" ).progressbar( "option", "max", e.total )
                               .progressbar( "value", e.loaded );

    }; 

});

如果您运行此示例并在本地上传文件到 http://127.0.0.1: 8000/,您会希望使用一个较大的文件。较小的文件上传速度太快,时间太短。较大的文件上传将使您能够在上传过程中看到以下内容。

操作方法...

注意

本书中的代码附带了一个最小的 Python 服务器,用于提供此演示上传页面并处理文件上传请求。该示例可以很容易地重新排列以与任何上传服务器配合使用,但是提供的 Python 服务器只需要安装 Python 即可。再次强调,这不是一个要求,但如果您渴望看到客户端代码运行的话,这只是一个方便的服务器。

工作原理...

该示例的目标是实时更新进度条小部件,随着文件上传进度的改变而改变。有几个插件可以提供这种功能,但如果您正在编写 jQuery UI 应用程序,最好统一使用进度条小部件。一旦文档准备就绪,我们首先创建用于显示文件上传进度的进度条小部件。 #upload-container 最初使用ui-helper-hidden类隐藏,因为我们不需要在上传正在进行之前显示上传进度。

接下来,我们设置我们上传表单的submit事件的事件处理程序。在执行任何其他操作之前,此处理程序防止默认表单提交。本质上,我们用我们自己的行为替换了浏览器实现的默认表单提交。我们需要覆盖此行为的原因是为了留在页面上,并对我们的进度条小部件应用更新。

接下来,我们设置实际将我们选定的文件发送到服务器的$.ajax()调用。我们从表单本身获取url参数。接下来的几个参数是发送多部分表单数据的先决条件,包括作为 Ajax 请求的一部分的选定文件。 xhr 选项是我们提供返回xhr对象的函数,内部由$.ajax()函数使用。这是我们截取xhr对象并附加其他行为的机会。我们主要感兴趣的是向onprogress事件添加新行为。

确保上传对象XMLHttpRequestUpload实际存在后,我们可以定义我们的onprogress事件处理程序函数。

首先,我们使用事件的loadedtotal属性计算实际上传百分比。接下来,我们显示进度容器,并使用uploadPercent中的值更新百分比标签。最后,我们确保上传进度条小部件的max选项设置为total,并使用value()方法设置进度条的当前值。

动画化进度变化

进度条小部件在设置valuemax选项时会改变其视觉外观。例如, value 的默认值为0max 的默认值为100。因此,当以这些值显示进度条小部件时,我们实际上并不看到图形化的条,然而这表示了进度百分比。但是,设置value选项将更新此条。如果条已经可见,则value选项的更改会导致进度条的宽度改变。使用默认进度条实现,这些更改会立即改变小部件。让我们看看如何修改小部件以支持进度条值之间的平滑过渡。

如何做...

我们将使用以下简单的标记作为我们进度条小部件实例的基础:

<div id="progress"></div>

这里是用于定制进度条小部件以支持动画更改进度的 JavaScript 代码:

(function( $, undefined ) {

$.widget( "ab.progressbar", $.ui.progressbar, {

    options: {
        animate: false
    },

    _refreshValue: function() {

        if ( !this.options.animate ) {
            return this._super();
        }

        var value = this.value(),
            percentage = this._percentage();

        if ( this.oldValue !== value ) {
            this.oldValue = value;
            this._trigger( "change" );
        }

        this.valueDiv.toggle( value > this.min )               .toggleClass( "ui-corner-right",
value === this.options.max )
                             .stop( true, true )
                             .animate( { width: percentage.toFixed( 0 ) + "%" }, 200 );

              this.element.attr( "aria-valuenow", value );

    }

});

})( jQuery );

$(function() {

    $( "#progress" ).progressbar( { animate: true } );

    var timer;

    var updater = function() {

        var value = $( "#progress" ).progressbar( "value" ) + 10,
            maximum = $( "#progress" ).progressbar( "option", "max" );

        if ( value >= maximum ) {
            $( "#progress" ).progressbar( "value", maximum );
            return;
        }

        $( "#progress" ).progressbar( "value", value );
        timer = setTimeout( updater, 700 );

    };

    timer = setTimeout( updater, 700 );

});

此示例包括一个更新器,将每 0.7 秒的间隔递增进度条值。您会注意到随着值的变化应用的平滑宽度过渡。与默认行为相比较,将animate选项设置为false,您现在将真正注意到每次更新值时进度条所做的视觉跳跃。

它是如何工作的...

我们的示例代码通过添加一个新的animate选项来扩展进度条小部件。新的animate选项默认为false。我们向进度条小部件引入的另一个更改是_refreshValue()方法的新实现,该方法在value选项更改时由小部件内部调用。此方法负责使div元素progress上的可视宽度发生变化。这代表了valuemax之间的进度。

很多这段代码都是从_refreshValue()的原始实现中借鉴而来的,因为我们只做了些微的修改。首先,我们检查了我们添加到小部件中的animate选项是否为true值。如果不是,则我们继续使用原始实现。否则,我们使用相同的代码,但对应用宽度的方式进行了轻微调整。然后,我们调用stop(true, true)来完成当前动画并清除动画队列。接下来,我们不再像原始实现那样使用width()函数,而是通过调用animate()来设置宽度。

这还不是全部...

与往常一样,我们不局限于使用 jQuery 的animate()函数来对进度条值之间的视觉过渡应用效果。除了animate()函数之外,我们还可以将 CSS 过渡应用于进度条值。当然,缺点是并非所有浏览器都支持 CSS 过渡,并且我们涉及到特定于供应商的样式规则。尽管如此,让我们将先前的方法与使用 CSS 样式来动画进度条进行比较。

我们将使用相同的标记,但我们将向页面引入以下样式:

.ui-progressbar-animated > .ui-progressbar-value {
    transition: width 0.7s ease-out;
    -moz-transition: width .7s ease-out;
    -webkit-transition: width 0.7s ease-out;
    -o-transition: width 0.7s east-out;
}

这里是 JavaScript 代码的必要更改。它看起来与之前的代码类似。

(function( $, undefined ) {

$.widget( "ab.progressbar", $.ui.progressbar, {

    options: {
        animate: false
    },

    _create: function() {

        this._super();

        if ( !this.options.animate ) {
            return;
        }

        this.element.addClass( "ui-progressbar-animated" );

    }

});

})( jQuery );

$(function() {

    $( "#progress" ).progressbar( { animate: true } );

    var timer;

    var updater = function() {

        var value = $( "#progress" ).progressbar( "value" ) + 10,
            maximum = $( "#progress" ).progressbar( "option", "max" );

        if ( value >= maximum ) {
            $( "#progress" ).progressbar( "value", maximum );
            return;
        }

        $( "#progress" ).progressbar( "value", value );
        timer = setTimeout( updater, 700 );

    };

    timer = setTimeout( updater, 700 );

});

运行此示例将与先前的animate选项实现看起来并无太大不同。过渡行为将基本相同。这里的关键区别在于我们正在扩展主题框架。我们为进度条小部件引入了一个新的 CSS 类——ui-progressbar-animated。选择器.ui-progressbar-animated > .ui-progressbar-value,适用于进度条值div,即宽度发生变化的元素。而我们的新样式正是如此。它们在 0.7 秒的时间段内过渡宽度属性值的变化。

这种方法的主要受益者是 JavaScript 代码,因为进度条小部件的变化较少。例如,我们不再覆盖_refreshValue()方法。相反,我们正在覆盖_create()方法,并且如果animated选项为true,则在元素中添加ui-progressbar-animated类。这是我们新样式如何生效的方式。其余实例化小部件和值更新器的 JavaScript 与前一个示例没有任何不同。

创建进度指示器小部件

进度条小部件旨在显示某个过程的进度。最终目标是在创建小部件时指定的max选项,默认为100。如果我们事先知道正在处理的数据的大小,我们将使用max选项来反映此最终目标。但是,有时我们面临的情况是在客户端执行一些处理;或者,我们正在等待某个后端进程完成并将响应发送回客户端。例如,用户使用 API 启动了后端任务,现在他们正在等待响应。关键是,我们希望向用户说明正在进行进度,而不知道已经完成了多少进度。

为了显示进度正在进行,尽管不知道有多少进度,我们需要一个指示器小部件。我们可以编写自己的小部件来实现这一点,扩展进度条小部件,因为我们可以在那里重用许多组件。

如何做…

对于我们的进度指示器小部件,我们将使用与基本进度条小部件相同的 HTML。

<div id="indicator"></div>

接下来,我们需要对进度条的 CSS 样式进行一些轻微的调整。这些应用于进度条div内部的值栏。我们去掉了bordermargin,因为在来回滑动值栏时这样看起来更好。

.ui-progressbar > .ui-progressbar-value {
    border: none;
    margin: 0px;
}

现在,我们来实现进度指示器小部件。此代码还将创建我们的进度指示器小部件的实例。

(function( $, undefined ) {

$.widget( "ab.progressindicator", $.ui.progressbar, {

    _create: function() {

        this._super();
        this.value( 40 );
        this.element.removeClass( "ui-corner-all" );
        this.valueDiv.removeClass( "ui-corner-right ui-corner-left" );

        var self = this,
            margin = ( this.element.innerWidth() - this.valueDiv.width() ) + "px";

        var _right = function() {

            self.valueDiv.animate(
                { "margin-left": margin },
                { duration: 1000, complete: _left }
            );

        };

        var _left = function() {

            self.valueDiv.animate(
                { "margin-left": "0px" },
                { duration: 1000, complete: _right }
            );

        };

        _right();

    },

    _destroy: function() {

        this.valueDiv.stop( true, true );
        this._super();

    }

});

})( jQuery );

$(function() {

    $( "#indicator" ).progressindicator();

});

如果您在浏览器中查看此进度指示器小部件,您将看到它通过来回滑动进度条小部件的值栏来进行动画处理,表示正在发生某事。

如何做…

它的工作原理…

我们创建了一个新的进度指示器小部件,继承了进度条小部件的功能。进度指示器小部件的目标是获取进度值栏div,在其中设置宽度,并在进度条容器div内滑动。视觉上,这表示幕后正在发生某事。这种图形化描述活动是对用户普遍令人放心的,因为它给人一种正在发生某事的感觉,并且应用程序没有崩溃。

在新进度指示器小部件的定义中,我们要重写的第一个方法是进度条的_create()方法。在这里,我们调用进度条小部件的原始构造函数,因为我们在开始进行更改之前需要所有的 UI 组件就位。接下来,我们使用value()方法为值条div设置宽度。我们在progressindicator()构造函数中硬编码了此值,只是因为使用此小部件的开发人员没有必要更改它;我们只需要设置元素的宽度。为了进一步简化此小部件,我们从元素中删除了角类。我们可以留下它们,但是在动画条时我们将不得不处理几种角例,因为我们追求的是一个简单的小部件,一个不需要开发人员进行配置的小部件。

仍然在_create()方法内部,我们定义了两个用于执行动画的实用函数。正如你可能猜到的那样,_right()函数将进度值条向右滑动,而_left()函数将其向左滑动。我们在该小部件的valueDiv属性上调用了animate()jQuery 函数。_right()函数通过更新margin-left值将值div向右滑动。您会注意到,margin变量在_create()内部局部定义。这是通过计算我们在值div右侧有多少空间来完成的,这意味着我们将此值设置为margin-left以将其向右滑动。要再次将其向左滑动,我们只需在_left()函数中将margin-left CSS 属性设置回0px

通过在_create()方法的底部调用_right()来引导动画。通过将_left()作为初始动画的回调传递,进度指示器动画循环发生。同样,在_left()函数内部将_right()作为动画完成回调传递。此过程将继续直到小部件被销毁。我们的小部件重写了_destroy()方法,只是为了确保所有动画立即停止。这包括任何等待执行的排队动画。然后,我们通过调用原始的_destroy()实现来继续销毁小部件。

还有更多...

我们的进度指示器小部件的一个优点是它提供了一个非常简单的 API。您可以根据需要创建和销毁小部件,而无需处理任何中间步骤。理想情况下,这个小部件的寿命会非常短,可能只有一秒钟(刚好足够看到一个动画循环)。然而,有时候可能需要更长一点。如果这个小部件要长时间显示,它可能会对应用程序造成问题。jQuery 的animate()函数并不是设计成无限循环运行动画的。我们的小部件也不是设计成长时间显示的。问题在于animate()使用计时器,可能会大幅消耗客户端的 CPU 周期。这不仅可能对我们的应用程序造成破坏,还可能对在用户机器上运行的其他应用程序造成影响。

尽管这是一个相对较小的问题,让我们来看看我们的进度指示器小部件的另一种实现方式,即使用 CSS 动画。以下是我们如何在 CSS 中定义动画的方式:

.ui-progressindicator > .ui-progressbar-value {
    border: none;
    margin: 0px;
    animation: indicator 2s ease-in-out infinite;
    -moz-animation: indicator 2s ease-in-out infinite;
    -webkit-animation: indicator 2s ease-in-out infinite;
}

@keyframes indicator {
    0%   { margin-left: 0px; }
    50%  { margin-left: 108px; }
    100% { margin-left: 0px; }
}

@-moz-keyframes indicator {
    0%   { margin-left: 0px; }
    50%  { margin-left: 108px; }
    100% { margin-left: 0px; }
}

@-webkit-keyframes indicator {
    0%   { margin-left: 0px; }
    50%  { margin-left: 108px; }
    100% { margin-left: 0px; }
}

@-o-keyframes indicator {
    0%   { margin-left: 0px; }
    50%  { margin-left: 108px; }
    100% { margin-left: 0px; }
}

并且,这是我们的progressindicator小部件的修改后的 JavaScript 实现,它知道如何利用先前的 CSS:

(function( $, undefined ) {

$.widget( "ab.progressindicator", $.ui.progressbar, {

  _create: function() {

        this._super();
        this.value( 40 );
        this.element.addClass( "ui-progressindicator" )
                    .removeClass( "ui-corner-all" );
        this.valueDiv.removeClass( "ui-corner-right ui-corner-left" );

    },

    _destroy: function() {

        this.element.removeClass( "ui-progressindicator" );
        this._super();

    }

});

})( jQuery );

$(function() {

    $( "#indicator" ).progressindicator();

});

现在,如果你在浏览器中查看这个小部件的修改版本,你应该会发现与以前的实现相比几乎完全一样的结果。当然,关键的区别在于动画是在 CSS 中指定并直接由浏览器执行。与基于 JavaScript 的对应物相比,浏览器可以更有效地处理这些类型的 CSS 动画。浏览器只需要一次读取动画规范,然后在内部运行动画,使用本机代码而不是执行 JavaScript 并直接操作 DOM。我们可以让这个版本运行一整天,浏览器会愉快地继续运行。

但是这个版本的进度指示器并不是没有缺点的。首先,让我们仔细看看 CSS。事实上,我们依赖 CSS 动画本身并不是最好的选择,因为不同浏览器对其支持存在差异。在这里,通过我们的样式,我们将自己陷入了浏览器厂商前缀混乱的困境。总的来说,支持还不错,因为只有 IE 不支持 CSS 动画;但是动画的定义很直接。在.ui-progressindicator > .ui-progressbar-value选择器中,我们指定了指示器动画将运行2秒,并且会无限重复。@keyframes指示器动画指定了margin-left属性本身的变化方式。

在 JavaScript 中,你会注意到代码本身要简单得多。这是因为它现在负责的事情要少得多。主要是,在创建时需要将 ui-progressindicator 类添加到小部件的 DOM 元素上,并在销毁时删除该类。你还会注意到,在实现小部件的 JavaScript 代码中不再进行边距计算。相反,我们将这些数字移到了定义小部件动画的 CSS 中作为硬编码值。再次强调,这只是小部件设计者必须考虑的一个权衡。我们在 CSS 中交换了更高的维护成本以获得更高效的动画,并为我们的小部件提供了更简单的 JavaScript,以牺牲可疑的浏览器支持。

使用状态来警告阈值

进度条小部件不仅限于标记朝某个结束点的进展。它还可以用作某些资源利用的标记。例如,你的应用程序可能允许用户存储 100 MB 的图像数据。显示当前使用了多少容量可能是有意义的。进度条小部件是图形化显示此类资源利用情况的理想解决方案。更进一步,我们可能还希望警告用户关于使用阈值。也就是说,在某个百分比下,资源接近容量,但用户仍然有时间对此做出反应。

准备工作

为了演示,我们将为要显示的两个进度条小部件创建两个简单的 div 元素:

<span>CPU:</span>
<div id="cpu-utilization"></div>
<span>Memory:</span>
<div id="memory-utilization"></div>

如何做...

下面是扩展进度条小部件的 JavaScript 代码,提供了一个新的选项来指定阈值:

(function( $, undefined ) {

$.widget( "ab.progressbar", $.ui.progressbar, {

    options: {
        threshold: 0
    },

  _percentage: function() {

        var percentage = this._super(),
            threshold = this.options.threshold;

        if ( threshold <= 0 ) {
            return percentage;
        }

        if ( percentage > threshold ) {
            this.valueDiv.addClass( "ui-state-error" );
        }
        else {
            this.valueDiv.removeClass( "ui-state-error" );
        }

        return percentage;

  },

});

})( jQuery );

$(function() {

    $( "#cpu-utilization" ).progressbar( { threshold: 80 } );
    $( "#memory-utilization" ).progressbar( { threshold: 85 } );

    setInterval(function() {
        var cpu = Math.floor( ( Math.random() * 100 ) + 1 ),
            memory = Math.floor( ( Math.random() * 100 ) +1 );

        $( "#cpu-utilization" ).progressbar( "value", cpu );
        $( "#memory-utilization" ).progressbar( "value", memory );

    }, 1300);

});

我们在这里实例化了两个进度条小部件,并启动了一个基本的定时器间隔,每 1.30 秒更改一次两个进度条小部件的值。如果你在浏览器中查看此示例,你会注意到一个或两个进度条小部件将进入错误状态,因为值已超过提供的阈值。

如何做...

工作原理...

我们添加到进度条小部件的新 threshold 选项是一个以百分比表示的数字。这是进度条的阈值,在这个阈值上,状态会改变以向用户发出视觉警告。这是通过重写 _percentage() 方法来实现的。在这里,我们通过调用 _percentage() 的原始实现并将其存储在 percentage 中来获得实际的百分比值。然后,我们确保 threshold 值非零,并且计算出的百分比大于 threshold 值。每次更新值时,进度条小部件都会内部调用 _percentage() 方法,并且视觉显示会发生变化。因此,在我们的 _percentage() 实现中,如果超过阈值,我们将 ui-state-error 类添加到 valueDiv 元素中,该元素是进度条内部移动的图形条。否则,我们低于阈值,并且必须确保删除 ui-state-error 类。

一旦我们创建了两个小部件,我们就使用 setInterval() 不断为两个进度条分配一个随机值。您可以坐下来观看进度条小部件如何根据输入的数据是否跨越我们指定的阈值而改变状态。在这种情况下,#cpu-utilization 进度条的阈值为 80%,而 #memory-utilization 进度条的阈值为 85%

给进度条添加标签

反映进度百分比变化宽度的图形条表现得很好。进度条小部件的强大之处在于一眼就能看到已经完成了多少进度,或者正在利用多少资源。但有时候我们可能需要一些关于百分比的准确度,即显示底层百分比的标签。

进度条小部件具有在进度条容器内显示标签的功能,这比在小部件外部显示百分比标签更直观。让我们看看如何扩展主题 CSS,为小部件提供额外的标记,并扩展进度条以利用这些新的附加功能来显示标签。

如何操作...

我们首先为我们的两个进度条小部件创建 HTML。

<span>Network:</span>
<div id="network-utilization">
    <div class="ui-progressbar-label"></div>
</div>
<span>Storage:</span>
<div id="storage-utilization">
    <div class="ui-progressbar-label"></div>
</div>

接下来,我们将添加进度条标签所需的 CSS 类。

.ui-progressbar-label {
    float: left;
    width: 100%;
    text-align: center;
    margin-top: 5px;
    font-weight: bold;
}

最后,我们将扩展进度条小部件本身,将这个新的 HTML 和新的 CSS 绑定在一起。

(function( $, undefined ) {

$.widget( "ab.progressbar", $.ui.progressbar, {

    _create: function() {
        this.$label = this.element.find( ".ui-progressbar-label" );
        this._super();

    },

    _destroy: function() {

        this.$label.remove();

        this._super();

    },

  _refreshValue: function() {
        this.$label.text( this._percentage().toFixed( 0 ) + "%" );
        this._super();

  },

});

})( jQuery );

$(function() {

    $( "#network-utilization" ).progressbar({
        value: 746586112,
        max: 1073741824
    });

    $( "#storage-utilization" ).progressbar({
        value: 24696061952,
        max: 107374182400
    });

});

您现在可以在浏览器中查看这两个进度条,您会注意到两个标签显示百分比值的位置位于小部件的中心。

如何操作...

它是如何工作的...

默认情况下,进度条小部件不支持标签,因此我们必须将标签 div 放在进度条 div 中。我们还给这个新的标签 div 添加了 ui-progressbar-label 类,这与 jQuery UI 主题命名规范一致。这个类实际上有两个作用:在我们引入的小部件自定义中,我们使用这个类来搜索标签 div 并应用标签样式。

ui-progressbar-label 中指定的 CSS 规则有助于将标签文本定位在进度条元素的中间。我们给标签 div 一个宽度为 100%,并使用 text-align 属性水平对齐文本。最后,我们使标签的 font-weightbold 以使其突出显示;否则,在进度条的背景下很难看到它。

我们在这里介绍的进度条小部件的自定义 JavaScript 实现覆盖了 _create() 方法。我们创建了一个称为 labelDiv 的新实例变量,它存储对我们新元素的引用。然后我们调用原始的 _create() 实现,构造函数继续正常进行,创建我们的新标签元素旁边的值 div。我们还重写了 _refreshValue() 方法以更新 labelDiv 的内容。_refreshValue() 方法在任何时候内部被小部件调用,当值改变并且进度条小部件需要更新值显示时,会更新 labelDiv 的值。我们通过在恢复 _refreshValue() 的原始实现之前使用 _percentage() 数字来扩展此行为。

还有更多...

我们实施进度条标签的这种方法可能遇到的一个潜在问题是,我们必须改变 HTML 结构。这违反了 DRY 原则,因为我们为每个创建的进度条小部件添加的每个标签 div 都是完全相同的。此外,我们可能希望为已存在于应用程序中的进度条小部件应用标签。改变已经正常工作的小部件的 HTML 不是最好的方法。让我们想想如何改进之前的代码。

我们创建的用于定位和样式化标签元素的 CSS 是可以的。它遵循正确的命名约定,并适用于所有进度条小部件实例。我们想要更改的是用于实例化带有显示的标签的进度条小部件的必要标记。问题是如何。理想情况下,通过一个选项,让开发人员切换标签的显示和隐藏。然后小部件本身将负责在必要时插入标签 div,因为它对于小部件的所有实例都是相同的,这反过来意味着最小的 JavaScript 代码。

让我们看一下简化的标记,遵循与之前相同的例子:


<span>Network:</span>
<div id="network-utilization"></div>
<span>Storage:</span>
<div id="storage-utilization"></div>

我们现在回到了进度条小部件在我们引入修改之前期望的原始标记。 现在让我们更新小部件代码以利用这个标记,通过添加一个新选项。

(function( $, undefined ) {

$.widget( "ab.progressbar", $.ui.progressbar, {

    options: {
        label: false
    },

    _create: function() {

        if ( !this.options.label ) {
            return this._super();
        }

        this.$label = $( "<div/>" ).addClass( "ui-progressbar-label" )
                                   .appendTo( this.element );

        this._super();

    },

    _destroy: function() {

        if ( !this.options.label ) {
            return this._super();
        }

        this.$label.remove();

        this._super();

    },

    _refreshValue: function() {

        if ( !this.options.label ) {
            return this._super();
        }

        this.$label.text( this._percentage().toFixed( 0 ) + "%" );

        this._super();

    },

});

})( jQuery );

$(function() {

    $( "#network-utilization" ).progressbar({
        value: 746586112,
        max: 1073741824,
        label: true
    });

    $( "#storage-utilization" ).progressbar({
        value: 24696061952,
        max: 107374182400
    });

});

在这里,我们通过新的label选项扩展了进度条小部件,该选项默认为false。 思路是当这个值为true时,我们将label div插入到进度条容器中。 我们对_create()_refreshValue()方法的修改基本与先前的代码相同,只是现在我们在执行自定义行为之前检查label选项是否已打开。 正如您所看到的,我们将这个新的标签选项提供给了#network-utilization div,但没有提供给#storage-utilization div。

更多内容请参考...

第八章:使用滑块

在本章中,我们将涵盖:

  • 控制滑块手柄的大小

  • 移除焦点轮廓

  • 使用主滑块和子滑块

  • 标记步进增量

  • 获取范围值

  • 更改滑块方向

介绍

滑块部件几乎就像一个用户可以操纵的进度条。滑块给用户一个手柄,可以沿平面拖动以产生所需值。这在处理表单值时尤其有用。滑块部件默认具有有用的选项,如更改方向的能力和允许用户选择值范围。在本章中,我们将看看通过添加新选项或附加事件处理函数来调整滑块部件的各种方法。我们还将研究一些视觉调整以及滑块实例如何相互通信。

控制滑块手柄的大小

用于控制滑块位置的滑块手柄,由鼠标拖动,是一个正方形。也就是说,宽度与高度相同,而我们可能想要不同形状的滑块手柄。在水平滑块的情况下,即默认方向,让我们看看如何通过覆盖部件 CSS 样式来改变滑块手柄的形状,以满足我们应用程序的需求。

准备好...

我们将创建的 HTML 是两个滑块部件。我们还将为它们添加标签,并将它们各自包装在容器 div 元素中以控制布局。

<div class="slider-container">
    <span>Treble:</span>
    <div id="treble"></div>
</div>
<div class="slider-container">
    <span>Bass:</span>
    <div id="bass"></div>
</div>

如何做...

这是用于自定义滑块手柄的 CSS。这覆盖了部件 CSS 中定义的值,因此应包含在 jQuery UI 样式表之后的页面中:

.ui-slider-horizontal .ui-slider-handle {
    width: 0.8em;
    height: 1.6em;
    top: -0.48em;
}

以下是用于创建两个滑块部件实例的 JavaScript 代码:

$(function() {

    $( "#treble" ).slider();
    $( "#bass" ).slider();

});

作为参考,这是应用我们自定义 CSS 前两个滑块部件的外观:

如何做...

这是应用我们自定义 CSS 后的相同两个滑块部件:

如何做...

它的工作原理...

如您所见,手柄变得更高,延伸到滑块边界之外。这为用户提供了更大的点击和拖动滑块手柄的表面积。我们引入的确切尺寸变化是任意的,可以根据每个应用程序进行调整。

.ui-slider-horizontal .ui-slider-handle 选择器覆盖了部件 CSS 中定义的三个属性。宽度被改变为 0.8em,使其略微变细。height 属性的值被改为 1.6em,使其变得更高。当我们使用 height 属性使手柄变高时,我们将其向下推,以使其不再与滑块对齐。为了弥补高度变化,我们通过减少 top 值来将其拉回上来,直到 -0.48em

移除焦点轮廓

大多数浏览器在接收到焦点时在元素周围显示虚线或实线轮廓。这不是用户界面样式的一部分,而是浏览器内置的辅助功能特性。例如,滑块手柄周围的这种强制视觉显示并不总是理想的。让我们看看我们如何取消滑块手柄的默认浏览器行为。

如何做到...

我们可以使用任何基本的div元素来构建我们的示例滑块小部件。所以让我们直接跳转到我们的自定义滑块小部件 CSS。

.ui-slider-handle-no-outline {
    outline: 0;
}

现在,我们已经有了我们的滑块小部件的自定义实现和我们自定义滑块的一个实例。

(function( $, undefined ) {

$.widget( "ab.slider", $.ui.slider, {

    options: { 
        handleOutline: true
    },

    _create: function() {

        this._super();

        if ( this.options.handleOutline ) {
            return;
        }

        this.handles.addClass( "ui-slider-handle-no-outline" );

    }

});

})( jQuery );

$(function() {

    $( "#slider" ).slider({
        handleOutline: false,
    });

});

在对滑块小部件应用我们的更改之前,拖动手柄后轮廓看起来如下所示:

如何做到...

在对滑块小部件应用我们的更改后,拖动手柄后我们的滑块实例如下所示:

如何做到...

工作原理...

我们已经为滑块小部件添加了一个名为handleOutline的新选项。我们将此选项默认设置为true,因为始终支持原生浏览器行为是一个好主意。当此选项设置为false时,该选项会关闭此原生边框轮廓功能。它通过向滑块中的每个手柄元素添加ui-slider-handle-no-outline类来实现。一个滑块中可以有很多手柄,例如,一个范围滑块。因此,在_create()方法中,我们检查handleOutline选项是否为true,如果是,我们使用存储为该小部件属性的handles jQuery 对象来应用我们创建的新类。

类本身很简单,因为它只改变了一个属性。事实上,我们可以简单地将outline属性添加到ui-slider-handle类中,值为0,而不是创建一个新类。但是,我们选择的方法允许我们保持本地小部件样式不变,这样可以让轮廓浏览器功能为我们的小部件的每个实例切换打开或关闭。您还会注意到,即使没有本地浏览器轮廓,手柄也不会失去任何可访问性,因为 jQuery UI 状态类为我们处理了这个问题。

使用主滑块和子滑块

应用程序可能会使用一些可以进一步分解为较小值的数量。此外,用户可能需要控制这些较小值,而不仅仅是聚合值。如果我们决定使用滑块小部件来实现这个目的,我们可以想象子滑块观察主滑块的变化值。让我们看看如何实现这样一组滑块。我们将设计一个界面,允许我们分配该应用程序可以使用多少 CPU。这是主滑块。我们假设一个四核架构,因此我们将有四个依赖于主 CPU 滑块并观察主 CPU 滑块的子滑块。

如何做到...

这里是用于定义我们的五个滑块布局的 HTML。每个滑块都有自己的 div 容器,主要用于定义宽度和边距。在 div 容器内,我们有每个 CPU 的标签,它们的当前 MHz 分配和最大值。这也是放置每个滑块小部件的地方。

<div class="slider-container">
    <h2 class="slider-header">CPU Allocation:</h2>
    <h2 class="slider-value ui-state-highlight"></h2>
    <div class="ui-helper-clearfix"></div>
    <div id="master"></div>
</div>

<div class="slider-container">
    <h3 class="slider-header">CPU 1:</h3>
    <h3 class="slider-value ui-state-highlight"></h3>
    <div class="ui-helper-clearfix"></div>
    <div id="cpu1"></div>
</div>

<div class="slider-container">
    <h3 class="slider-header">CPU 2:</h3>
    <h3 class="slider-value ui-state-highlight"></h3>
    <div class="ui-helper-clearfix"></div>
    <div id="cpu2"></div>
</div>

<div class="slider-container">
    <h3 class="slider-header">CPU 3:</h3>
    <h3 class="slider-value ui-state-highlight"></h3>
    <div class="ui-helper-clearfix"></div>
    <div id="cpu3"></div>
</div>

<div class="slider-container">
    <h3 class="slider-header">CPU 4:</h3>
    <h3 class="slider-value ui-state-highlight"></h3>
    <div class="ui-helper-clearfix"></div>
    <div id="cpu4"></div>
</div>

接下来,我们有一些 CSS 样式来帮助对齐和定位这些组件。

.slider-container { 
    width: 200px;
    margin: 5px;
}

.slider-header {
    float: left;
}

.slider-value {
    float: right;
}

最后,我们有我们的 JavaScript 代码,该代码扩展了滑块小部件,为使用它的开发人员提供了两个新选项,parentpercentage。文档加载时,我们实例化了我们的 CPU 滑块小部件,并利用我们的新滑块功能来建立它们之间的适当关系。

(function( $, undefined ) {

$.widget( "ui.slider", $.ui.slider, {

    options: {
        parent: null,
        percentage: null
    },

    _create: function() {

        this._super();

        var parent = this.options.parent,
            percentage = this.options.percentage,
            $parent;

        if ( !( parent && percentage ) ) {
            return;
        }

        $parent = $( parent );

        this._reset( $parent.slider( "value" ) );

        this._on( $parent , { 
            slidechange: function( e, ui ) {
                this._reset( ui.value );
            }
        });

    },

    _reset: function( parentValue ) {

        var percentage = ( 0.01 * this.options.percentage ),
            newMax = percentage * parentValue,
            oldMax = this.option( "max" ),
            value = this.option( "value" );

        value = ( value / oldMax ) * newMax;

        this.option( "max", newMax );
        this.option( "value", value );

    }

});

})( jQuery );

$(function() {

    function updateLabel( e, ui ) {

        var maxValue = $( this ).slider( "option", "max" )
                                .toFixed( 0 ),
            value = $( this ).slider( "value" )
                             .toFixed( 0 ) + " MHz" +
                                             " / " + 
                                             maxValue + 
                                             "MHz";

        $( this ).siblings( ".slider-value" ).text( value );

    }

    $( "#master" ).slider({
        range: "min",
        value: 379,
        min: 1,
        max: 2400,
        create: updateLabel,
        change: updateLabel
    });

    $( "#cpu1" ).slider({
        parent: "#master",
        percentage: 25,
        range: "min",
        min: 0,
        create: updateLabel,
        change: updateLabel
    });

    $( "#cpu2" ).slider({
        parent: "#master",
        percentage: 35,
        range: "min",
        min: 0,
        create: updateLabel,
        change: updateLabel
    });

    $( "#cpu3" ).slider({
        parent: "#master",
        percentage: 15,
        range: "min",
        min: 0,
        create: updateLabel,
        change: updateLabel
    });

    $( "#cpu4" ).slider({
        parent: "#master",
        percentage: 25,
        range: "min",
        min: 0,
        create: updateLabel,
        change: updateLabel
    });

});

在浏览器中查看结果滑块小部件,并调整一些子 CPU 值。您会注意到标签更新已经改变,并且每个 CPU 都有其自己的 CPU 分配。

操作步骤...

现在,保持 CPU 值不变,尝试调整主 CPU 分配滑块。您会注意到每个子 CPU 滑块的当前值和最大值都会改变,但比例是保持不变的。这意味着如果我们设置 CPU 1 使用总体 CPU 分配的 10%,即使总体分配增加或减少,它仍将继续使用 10%。

操作步骤...

工作原理...

在我们为 CPU 滑块创建的每个容器 div 元素中,我们都有一个名为 slider-value 的头部,用于显示滑块的当前值以及最大值。这是一个需要在大多数情况下考虑的重要补充,而滑块小部件则非常适合让用户更改值,但他们需要特定的反馈来显示他们操作的结果。在这个例子中,更改主滑块会更新五个标签,进一步凸显了在用户能够看到的滑块外部标记特定滑块值的必要性。

我们在滑块小部件中新增了两个选项,parentpercentage。这两个选项彼此相关,基本上可以理解为"此滑块的最大值是其父级滑块值的百分比"。在 _create() 方法中,我们在继续之前会检查这两个选项是否有实际值,因为它们默认为null。如果没有值,我们已经使用 _super() 方法调用了原始滑块构造函数,因此我们可以安全地返回。

另一方面,如果我们已经得到了一个父级滑块小部件和一个百分比,我们将调用_reset()方法,并将当前值传递给我们的父级滑块。这将可能更新此小部件的最大值和当前值。完成这些操作后,我们设置了一个观察者,用于观察父级滑块的更改。这是使用_on()方法完成的,我们在其中传递parent作为我们正在监听事件的元素以及配置对象。该对象具有一个slidechange事件,这是我们感兴趣的事件,以及回调函数。在回调函数内部,我们只是使用来自父级的更新值简单地调用了我们的_reset()方法。值得注意的是,我们必须使用_on()来注册我们的事件处理程序。如果销毁了子滑块,事件处理程序将从父级中删除。

_reset()方法接受来自父级滑块的值,并重置此子滑块的最大选项。我们在首次创建子元素和父元素值更改时都使用此方法。目标是保持当前值/最大值比率。这就是percent选项发挥作用的地方。由于这作为整数传递给小部件,我们必须将其乘以0.01。这是我们计算出该子级的新最大值的方法。一旦我们有了新的最大值,我们就可以将当前值放大或缩小。

最后,在文档准备就绪的事件处理程序中,我们实例化了五个滑块小部件,在其中定义了一个用于更新每个 CPU div 中标签的通用回调函数。这个函数被传递给了每个滑块小部件的创建和更改选项。我们还在这里使用了我们新定义的选项的值。每个子滑块都有一个独特的总 CPU 分配的百分比值,并且每个子元素都使用#master作为其父级

标记步长增量

滑块小部件可以传递一个步长值,该值确定用户可以滑动手柄的增量。如果未指定,步长选项为1,手柄会平滑地来回滑动。另一方面,如果步长值更加明显,比如10,我们会注意到随着移动手柄而手柄会吸附到位置。让我们看看我们如何扩展滑块小部件以使用户更好地感受到这些增量的位置。我们将使用刻度来在视觉上标记增量。

如何做...

我们将直接进入用于此小部件增强的自定义 CSS。用于滑块元素的基础div元素可以简单地是<div></div>

.ui-slider-tick {
    position: absolute;
    width: 2px;
    height: 15px;
    z-index: -1;
}

这是我们的 JavaScript 代码,扩展了滑块并使用新的ticks选项创建了小部件的实例:

(function( $, undefined ) {

$.widget( "ab.slider", $.ui.slider, {

    options: {
        ticks: false
    },

    _create: function() {

        this._super();

        if ( !this.options.ticks || this.options.step < 5 ) {
            return;
        }

        var maxValue = this.options.max,
            cnt = this.options.min + this.options.step,
            background = this.element.css( "border-color" ),
            left;

        while ( cnt < maxValue ) {

            left = ( cnt / maxValue * 100 ).toFixed( 2 ) + "%";

            $( "<div/>" ).addClass( "ui-slider-tick" )
                         .appendTo( this.element )
                         .css( { left: left,
                                 background: background } );

            cnt += this.options.step;

        }

    }

});

})( jQuery );

$(function() {

    $( "#slider" ).slider({
        min: 0,
        max: 200,
        step: 20,
        ticks: true
    });

});

查看此滑块小部件,我们可以看到我们指定的步长20在滑块下方使用刻度标记来表示。

如何做...

工作原理...

让我们检查我们已经引入到滑块小部件中的附加功能。我们添加了ticks布尔选项,默认情况下关闭。当这个选项为真时,告诉小部件使用刻度标记显示步进增量。在_create()方法中,我们使用_super()调用了原始的_create()实现,因为我们希望滑块按照正常方式构造。然后,我们检查ticks选项是否已打开,以及step值是否大于5。如果已打开ticks选项并且我们有一个小于5step值,它们将看起来彼此靠近;所以我们简单地不显示它们。

计数器变量cnt控制着我们的刻度渲染循环,并初始化为min选项上方的第一个step。同样,循环在max选项值之前退出。这是因为我们不想在滑块的开头或结尾渲染刻度标记,而只想在中间部分显示。变量background用于从滑块小部件中提取border-color CSS 属性。我们实际上在这里所做的是将主题设置传递给我们要添加到小部件中的新元素。这允许主题被交换,刻度标记的颜色也会相应更改。

while循环内,我们正在创建代表刻度标记的div元素。left CSS 属性被计算为实际定位div,使其与用户移动手柄时的滑块手柄对齐。我们将ui-slider-tick CSS 类添加到div元素中,配置每个刻度标记的公共属性,包括z-index,将div的一部分推到主滑块栏的后面。

获取范围数值

滑块小部件可用于控制范围值。因此,用户不是在滑块轴上来回移动一个固定点,即手柄,而是在两个手柄之间来回移动。这两个点之间的空间表示范围值。但是我们如何计算这个数字呢?滑块小部件给我们提供了原始数据,即用户选择的上限和下限。我们可以在我们的事件处理程序中使用这些值来计算范围值。

准备工作...

我们将仅使用基本的滑块进行演示,但我们需要一些支持的 CSS 和 HTML 来包围滑块,以便在更改时显示范围值。以下是 CSS:

.slider-container { 
    width: 180px;
    margin: 20px;
}

.slider-container .slider-label {
    margin-bottom: 10px;
    font-size: 1.2em;
}

这是 HTML 代码:

<div class="slider-container">
    <div class="slider-label">
        <span>Range Value: </span>
        <strong id="range-value"></strong>
    </div>
    <div id="slider"></div>
</div>

操作方法...

我们将使用以下 JavaScript 代码创建slider实例。请注意,我们传递了支持范围选择的特定选项。

$(function() {

    $( "#slider" ).slider({
        min: 0,
        max: 600,
        values: [280, 475],
        range: true,
        create: function( e, ui ) {
            var values = $( this ).data( "uiSlider" ).values();
            $( "#range-value" ).text( values[1] - values[0] );
        },
        change: function( e, ui ) {
            $( "#range-value" ).text( ui.values[1] - ui.values[0] );
        }
    });

});

现在,当您在浏览器中查看此滑块时,您会注意到范围值显示为小部件外的标签。而且,如果您移动滑块手柄中的任何一个,标签将反映更改的范围值。

操作方法...

工作原理...

在这个例子中,我们正在创建一个简单的滑块小部件,它使用一系列值而不是单个值。我们通过将值数组传递给小部件构造函数,并将range值传递给构造函数,以此来实现。这就是小部件知道要使用两个手柄而不是一个,并填充它们之间的空间的方式。我们还将滑块构造函数与两个事件回调函数一起传递:一个用于create事件,另一个用于change事件。

这两个回调函数执行相同的操作:它们计算范围值并将其显示在我们的#range-value标签中。然而,这两个回调函数以稍微不同的方式实现相同的逻辑。create回调函数不包含ui对象的values数组,该数组用于保存小部件数据。因此,在这里我们的解决方法是使用uiSlider数据,该数据保存了 JavaScript 滑块小部件实例,以便访问values()方法。这将返回传递给 change 事件回调函数的ui对象中找到的相同数据。

我们在这里计算的数字只是第一个手柄的值减去第二个手柄的值。例如,如果我们在表单中使用这样的滑块,API 可能不关心由两个滑块手柄表示的两个值,而只关心由这两个数字导出的范围值。

更改滑块方向

默认情况下,滑块小部件将水平呈现。我们可以通过orientation选项轻松将滑块方向更改为垂直布局。

操作步骤...

我们将使用以下 HTML 来定义我们的两个小部件。第一个滑块将是垂直的,而第二个则使用默认的水平布局:

<div class="slider-container">
    <div id="vslider"></div>
</div>

<div class="slider-container">
    <div id="hslider"></div>
</div>

接下来,我们将使用以下 JavaScript 代码实例化这两个小部件:

$(function() {

    $( "#vslider" ).slider({
        orientation: "vertical",
        range: "min",
        min: 1,
        max: 200,
        value: 128
    });

    $( "#hslider" ).slider({
        range: "min",
        min: 0,
        max: 200,
        value: 128
    });

});

如果您在浏览器中查看这两个滑块,您可以看到垂直布局和默认水平布局之间的对比:

操作步骤...

工作原理...

我们在这里创建的两个滑块小部件,#vslider#hslider,在内容上是相同的。唯一的区别是#vslider实例是使用orientation选项设置为vertical创建的。#hslider实例没有指定orientation选项,因此使用默认的horizontal。它们之间的关键区别在于布局,正如我们的示例中明显的那样。布局本身由ui-slider-verticalui-slider-horizontalCSS 类控制,这两个类是互斥的。

控制滑块的方向是有价值的,这取决于你想把小部件放在 UI 上下文中的位置。例如,包含元素可能没有太多的水平空间,所以在这里使用垂直方向选项可能是个不错的选择。然而,要小心动态改变滑块的方向。手柄有时会从滑块条中脱离。因此,在设计时最好确定方向。

第九章:使用旋转器

在本章中,我们将涵盖:

  • 移除输入焦点轮廓

  • 为本地文化格式化货币

  • 为本地文化格式化时间

  • 控制值之间的步骤

  • 指定旋转溢出

  • 简化旋转器按钮

介绍

在本章中,我们将使用旋转器。 旋转器 只不过是文本input元素上的装饰品。但与此同时,它还有很多其他用途。例如,旋转器在本章中将有助于将数字格式化为本地文化。我们还将探讨旋转器小部件提供的一些选项,以及如何扩展和改进这些选项。最后,我们将看一些修改旋转器小部件外观和感觉的方法。

移除输入焦点轮廓

大多数浏览器在用户从中获得焦点时,将自动在input元素周围应用输入焦点轮廓。当用户单击input元素或通过标签到达时,元素会获得焦点。旋转器小部件本质上是一个带有装饰的input元素。这包括利用 CSS 主题框架中的内在 jQuery 状态类的能力。虽然浏览器的自动聚焦行为对于单独的input元素可能效果很好,但是这些焦点环可能会使旋转器看起来有点凌乱。让我们看看如何删除自动焦点轮廓,同时保持相同的可访问性水平。

如何做...

对于这个示例,我们将创建一个简单的input元素。以下是 HTML 结构的样子。

<div class="spinner-container">
    <input id="spinner"/>
</div>

这是与我们的小部件修改一起使用的自定义 CSS,以移除焦点轮廓。

.ui-spinner-input-no-outline {
    outline: 0;
}

最后,这是我们的 JavaScript 代码,它修改了旋转器小部件的定义,并创建了一个实例,浏览器不会自动应用任何轮廓。

(function( $, undefined ) {

$.widget( "ab.spinner", $.ui.spinner, {

    options: {        
inputOutline: true    
},

    _create: function() {

        this._super();

        if ( this.options.inputOutline ) {            
return;        
}

        this.element.addClass( "ui-spinner-input-no-outline" );
        this._focusable( this.uiSpinner );

    }
});

})( jQuery );

$(function() {

    $( "#spinner" ).spinner( { inputOutline: false } );

});

为了让您更好地了解我们引入的更改,这就是我们在对旋转器定义进行修改之前创建的旋转器小部件的外观。

如何做...

在这里,您可以清楚地看到input元素具有焦点,但是我们可以不使用双重边框,因为它与我们的主题不太匹配。以下是在引入我们的更改后处于焦点状态的相同小部件的修改版本。

如何做...

我们不再有焦点轮廓,当小部件获得焦点时,小部件仍然会在视觉上更改其状态。只是现在,我们正在使用 CSS 主题中的状态类更改外观,而不是依赖浏览器为我们完成。

它是如何工作的...

处理移除轮廓的 CSS 类,ui-spinner-input-no-outline类,非常容易理解。我们只需将outline设置为0,这将覆盖浏览器的默认操作方式。我们自定义的旋转器小部件知道如何利用这个类。

我们已经向旋转器小部件添加了一个新的inputOutline选项。如果设置为false,此选项将向input元素应用我们的新 CSS 类。但是,默认情况下,inputOutline默认为true,因为我们不希望默认情况下覆盖默认浏览器功能。此外,我们也不一定想要默认情况下覆盖默认的旋转器小部件功能。相反,最安全的方式是提供一个选项,当显式设置时,改变默认设置。在我们的_create()方法的实现中,我们调用旋转器构造函数的原始实现。然后,如果inputOutline选项为true,我们应用ui-spinner-input-no-outline类。

再次,请注意,我们最后要做的事情是将this.uiSpinner属性应用于_focusable()方法。原因是,我们需要弥补失去的可访问性;浏览器不再应用轮廓,因此当小部件获得焦点时,我们需要应用ui-state-focus类。_focusable()方法是在基本小部件类中定义的一个简单辅助方法,因此对所有小部件都可用,使传递的元素处理焦点事件。这比自己处理事件设置和撤消要简单得多。

格式化本地文化的货币

可以将旋转器小部件与Globalize jQuery 库一起使用。 Globalize 库是 jQuery 基金会的一项努力,旨在标准化 jQuery 项目根据不同文化格式化数据的方式。文化是根据文化规范格式化字符串、日期和货币的一组规则。例如,我们的应用程序应该将德语日期和货币与法语日期和货币区分对待。这就是我们能够向旋转器小部件传递culture值的方式。让我们看看如何使用 Globalize 库与旋转器小部件将货币格式化为本地文化。

操作步骤...

当我们的应用程序在多个区域设置中运行时,第一件需要做的事情就是包含globalize库。每种文化都包含在自己的 JavaScript 文件中。

<script src="img/globalize.js"
  type="text/javascript"></script>
<script src="img/globalize.culture.de-DE.js"
  type="text/javascript"></script>
<script src="img/globalize.culture.fr-CA.js"
  type="text/javascript"></script>
<script src="img/globalize.culture.ja-JP.js"
  type="text/javascript"></script>

接下来,我们将定义用于显示文化选择器的 HTML,由单选按钮组成,并且用于显示货币的旋转器小部件。

<div class="culture-container"></div>
<div class="spinner-container">
    <input id="spinner"/>
</div>

最后,我们有用于填充culture选择器、实例化旋转器小部件并将更改事件绑定到文化选择器的 JavaScript 代码。

$(function() {

    var defaultCulture = Globalize.cultures.default;

    $.each( Globalize.cultures, function( i, v ) {

      if ( i === "default" ) {
        return;
      }

       var culture = $( "<div/>" ).appendTo( ".culture-container" );

       $( "<input/>" ).attr( "type", "radio" )
          .attr( "name", "cultures" )
          .attr( "id", v.name )
          .attr( "checked", defaultCulture.name === v.name )
          .appendTo( culture );

       $( "<label/>" ).attr( "for", v.name )
           .text( v.englishName )
           .appendTo( culture );

    });

    $( "#spinner" ).spinner({
        numberFormat: "C",
        step: 5,
        min: 0,
        max: 100,
        culture: $( "input:radio[name='cultures']:checked" )
          .attr( "id" )
    });

    $( "input:radio[name='cultures']" ).on
      ( "change", function( e ) {
        $( "#spinner" ).spinner( "option", "culture",
          $( this ).attr( "id" ) );
    });

});

当您首次在浏览器中查看此用户界面时,您会注意到英语是选定的文化,并且旋转器将相应地格式化货币。

操作步骤...

但是,文化的更改会导致旋转器小部件中的货币格式发生变化,如前所述。

操作步骤...

工作原理...

在 JavaScript 代码中,一旦 DOM 准备就绪,我们首先使用 Globalize.cultures 对象填充 culture 选择器。 Globalize 库根据可用的文化构建此对象;你会注意到可用文化选项与页面中包含的文化脚本之间存在直接关联。 我们将文化的名称存储为 id 属性,因为这是我们稍后传递给微调器小部件的内容。 Globalize.cultures 对象还具有默认文化,我们使用此值来确定页面首次加载时选择了哪个选项。

我们创建的微调器实例使用了一个 numberFormat 选项值为 C。 这个字符串实际上在渲染微调器值时直接传递给 Globalize.format() 函数。 接下来的三个选项,stepminmax,与任何数字微调器实例一样。 我们将 culture 选项设置为所选的默认文化,告诉微调器小部件如何格式化货币。 最后,我们设置了一个事件处理程序,每当文化选择更改时触发。 此处理程序将更新微调器小部件以使用新选择的文化。

为本地文化格式化时间

微调器小部件利用了 Globalize jQuery 项目;这是一项根据本地文化标准化数据格式的工作。 微调器小部件利用此库来格式化其值。 例如,指定 numberFormatculture 选项允许我们使用微调器小部件根据本地文化显示货币值。 然而,货币只是我们喜欢本地格式化的一个值; 时间是另一个值。 我们可以在微调器小部件中使用内置的 Globalize 功能来显示时间值。 我们需要在我们自己的部分上做更多工作来扩展小部件以正确地允许时间值。 实际上,让我们基于微调器创建我们自己的时间小部件。

如何实现...

首先,让我们看一下创建两个时间小部件所需的标记,我们将在其中显示多伦多时间和伦敦时间。 我们在这里不展示时区计算能力,只是在同一个 UI 中展示两种不同的文化。

<div class="spinner-container">
    <h3>Toronto</h3>
    <input id="time-ca" value="2:30 PM"/>
</div>

<div class="spinner-container">
    <h3>London</h3>
    <input id="time-gb" value="7:30 PM"/>
</div>

接下来,让我们看一下用于定义新时间小部件并创建两个实例的 JavaScript 代码。

( function( $, undefined ) {

$.widget( "ab.time", $.ui.spinner, {

    options: {
        step: 60 * 1000,
        numberFormat: "t"
    },

    _parse: function( value ) {

        var parsed = value;

        if ( typeof value === "string" && value !== "" ) {

            var format = this.options.numberFormat,
                culture = this.options.culture;

            parsed = +Globalize.parseDate( value, format );

            if ( parsed === 0 ) {
                parsed = +Globalize.parseDate( value,
                  format, culture );
            }

        }

        return parsed === "" || isNaN( parsed ) ? null : 
          parsed;

    },

    _format: function( value ) {
        return this._super( new Date( value ) );
    }

});

})( jQuery );

$(function() {

    $( "#time-ca" ).time({
        culture: "en-CA"
    });

    $( "#time-gb" ).time({
        culture: "en-GB"
    });

});

在浏览器中查看两个时间小部件,我们可以看到它们已按其各自的本地文化格式化。

如何实现...

工作原理...

让我们首先看一下用于定义时间小部件实例的两个输入元素。 注意 value 属性,它们都具有默认时间,使用相同的格式表示。 现在,让我们跳转到新时间小部件的定义。

你在这里首先注意到的是,我们使用小部件工厂在 ab 命名空间下定义了时间小部件。您还会注意到,我们正在扩展微调器小部件。这是因为实质上我们正在构建的是一个微调器,在这里有一些小但重要的区别。这实际上是一个很好的例子,说明了当设计从标准小部件集派生的 jQuery UI 小部件自定义时,您必须考虑的一些事情。在这种情况下,您应该保留原始小部件名称,即微调器,还是应该叫它其他名称,比如时间?可以帮助您指导这个决定的唯一事情是思考此小部件的使用方式。例如,我们本可以保持微调器小部件不变以显示这些受文化影响的时间值,但这意味着引入新的选项,并可能让使用该小部件的开发人员感到困惑。我们已经决定这里的用例很简单,我们应该尽可能少地允许时间以尽可能少的选项显示。

我们在此定义的选项并不是新的;stepnumberFormat 选项已经由微调器小部件定义,我们只是将它们设置为适合我们时间小部件的默认值。step 值将针对一个 timestamp 值递增,因此我们给它一个默认值,以秒为步长。numberFormat 选项指定微调器在解析和格式化输出时所期望的格式。

我们对微调器的扩展,_parse() 方法,是我们直接使用 Globalize 库解析时间字符串的地方。请记住,我们的输入具有相同的字符串格式。如果我们尝试解析一个格式不可识别的值,这就成为了一个问题。因此,我们尝试在不指定值所属文化的情况下解析时间值。如果这样不起作用,我们就使用附加到此小部件的文化。通过这种方式,我们可以使用一个格式指定初始值,就像我们在这里做的一样,并且我们可以动态更改文化;一切仍将正常工作。我们的_format()方法的版本很简单,因为我们知道值始终是一个时间戳数字,我们只需将一个新的 Date 对象传递回原始的微调器_format()方法即可。

最后,我们有两个时间小部件实例,其中一个传递了 en-CA 的文化,另一个传递了 en-GB

控制值之间的步长

有几种方法可以控制微调器小部件中的步骤。步骤是微调器小部件用来向上或向下移动其数字的值。例如,您经常会看到循环代码,它会增加一个计数器 cnt ++。在这里,步骤是一,这是微调器步骤值的默认值。更改微调器中的此选项很简单;我们甚至可以在创建小部件后更改此值。

我们可以采取其他措施来控制旋转器的步进行为。让我们看看增量选项,并看看这如何影响旋转器。

如何做...

我们将创建三个旋转器部件来演示增量选项的潜力。以下是 HTML 结构:

<div class="spinner-container">
    <h3>Non-incremental</h3>
    <input id="spin1" />
</div>

<div class="spinner-container">
    <h3>Doubled</h3>
    <input id="spin2" />
</div>

<div class="spinner-container">
    <h3>Faster and Faster</h3>
    <input id="spin3" />
</div>

下面是用于创建三个旋转器实例的 JavaScript 代码:

$(function() {

    $( "#spin1" ).spinner({
        step: 5,
        incremental: false
    });

    $( "#spin2" ).spinner({
        step: 10,
        incremental: function( spins ) {
            if ( spins >= 10 ) {
                return 2;
            }
            return 1;
        }
    });

    $( "#spin3" ).spinner({
        step: 15,
        incremental: function( spins ) {
            var multiplier = Math.floor( spins / 100 ),
                limit = Math.pow( 10, 10 );
            if ( multiplier < limit && multiplier > 0 ) {
                return multiplier;
            }
            return 1;
        }
    });

});

在您的浏览器中,这三个旋转器部件应该看起来是这样的。

如何做...

工作原理...

我们创建了三个不同的旋转器实例,它们在用户按住其中一个旋转按钮时的行为不同。#spin1旋转器的步长值为5,并且将始终将旋转器值递增5。您可以通过按住旋转按钮来尝试这一点。您会注意到这将花费您很长时间才能达到一个较大的整数值。

incremental选项接受一个布尔值,就像我们在第一个旋转器中看到的那样,但它还接受一个callback函数。#spin2旋转器的步长值为10,但它将根据我们传递给增量选项的函数而改变。我们定义的这个incremental callback函数通过用户按住旋转按钮的旋转次数传递。我们从这里正常开始,前10次旋转,然后我们从那时起加速返回2而不是1。当我们返回2时,我们的步长值变为20,因为该函数的返回值是一个乘数。但它只在用户按住旋转按钮时使用;此函数不会永久改变step选项。

我们的最后一个旋转器实例,#spin3,也使用了一个incremental callback函数。然而,这个函数会随着用户持续旋转而使用一个逐渐变大的值。每旋转一百次,我们就增加乘数,也增加步长。后者的递增函数在旋转器值本身变大时非常有用,我们可以控制步长变化的速度。

更多内容...

我们刚刚看到了如何控制旋转器部件的值步进。step选项决定了在给定旋转时值在任一方向上移动的距离。当用户按住旋转按钮时,我们可以使用incremental选项来计算步长值。这有助于加快或减慢旋转到给定目标值所需的时间。

另一种方法是改变旋转之间的实际时延。如果您想要在用户按住旋转按钮时减慢旋转速度,这可能会很方便。让我们看一个如何改变旋转延迟的例子。以下是 HTML 结构:

<div class="spinner-container">
    <h3>Default delay</h3>
    <input id="spin1" />
</div>

<div class="spinner-container">
    <h3>Long delay</h3>
    <input id="spin2" />
</div>

<div class="spinner-container">
    <h3>Longer delay</h3>
    <input id="spin3" />
</div>

这是自定义旋转器部件定义,以及使用不同旋转值的三个实例。

( function( $, undefined ) {

$.widget( "ab.spinner", $.ui.spinner, {

    options: {
        spinDelay: 40
    },

    _repeat: function( i, steps, event ) {

        var spinDelay = this.options.spinDelay;

        i = i || 500;

        clearTimeout( this.timer );
        this.timer = this._delay(function() {
            this._repeat( spinDelay, steps, event );
        }, i );

        this._spin( steps * this.options.step, event );

     }

});

})( jQuery );

$(function() {

    $( "#spin1" ).spinner();

    $( "#spin2" ).spinner({
        spinDelay: 80
    });

    $( "#spin3" ).spinner({
        spinDelay: 120
    });

});

您可以在浏览器中尝试这些旋转器中的每一个,并观察旋转延迟的对比。

更多内容...

我们已将spinDelay选项添加到微调器小部件中,以便可以指定延迟的毫秒数。为了实际使用此选项,我们必须对其中一个核心微调器小部件方法进行一些更改。当用户按住微调器按钮时,内部使用_repeat()方法。它实际上使用很少的代码执行了大量工作。基本上,目标是重复给定的事件,直到用户松开按钮并且旋转应该停止。但是,我们不能仅仅重复调用_spin(),而不添加任何延迟,否则用户每次更新文本输入时都会看到模糊的内容。因此,微调器正好利用_delay()方法来实现此目的。_delay()方法为过去的函数设置延迟执行,并在基本小部件类中定义;所有小部件都可以访问_delay()

我们的_repeat()方法版本与原始版本几乎相同,除了我们现在不再硬编码旋转之间的延迟;我们现在从spinDelay选项中获取它。

指定旋转溢出

微调器小部件将愉快地让用户无限地旋转。当达到 JavaScript 整数限制时,它甚至会将显示更改为使用指数表示法,这没问题。几乎没有应用程序需要担心这些限制。事实上,最好为应用程序制定一些有意义的限制。也就是说,指定min边界和max边界。

这很有效,但是如果我们在处理溢出的微调器中插入一些逻辑,它甚至可以工作得更好,当用户想要超出边界时。与默认行为停止旋转不同,我们只是将它们发送到相反的边界,但是以相同的方向开始旋转。最好的方法是将这些约束想象成默认情况下,微调器的最小 - 最大边界就像一条直线。我们想让它看起来更像一个圆。

如何做...

我们将有两个微调器小部件,第一个使用默认边界约束逻辑,第二个使用我们自己定义的行为。以下是用于创建这两个小部件的 HTML 结构:

<div class="spinner-container">
    <h3>Default</h3>
    <input id="spin1" />
</div>

<div class="spinner-container">
    <h3>Overflow</h3>
    <input id="spin2" />
</div>

这里是文档加载后用于实例化两个微调器的 JavaScript 代码:

$(function() {

    $( "#spin1" ).spinner({
        min: 1,
        max: 100
    });

    $( "#spin2" ).spinner({
        minOverflow: 1,
        maxOverflow: 100,
        spin: function( e, ui ) {

            var value = ui.value,
              minOverflow = $( this ).spinner
                ( "option", "minOverflow" ),
                  maxOverflow = $( this ).spinner
                    ( "option", "maxOverflow" );

            if ( value > maxOverflow ) {
                $( this ).spinner( "value", minOverflow );
                return false;
            }
            else if ( value < minOverflow ) {
                $( this ).spinner( "value", maxOverflow );
                return false;
            }

        }
    });

});

以下是浏览器中的两个微调器。您将看到,后一个微调器处理边界溢出的方式与默认实现不同。

如何做...

工作原理...

#spin1微调器达到边界之一,即1100时,旋转将停止。另一方面,#spin2微调器将从另一端开始旋转。您会注意到我们在这里传递了两个非标准的微调器选项;minOverflowmaxOverflow。这些实际上不会像minmax一样约束微调器的边界。我们之所以故意添加这些新选项,是因为我们不希望常规约束逻辑触发。

我们为这个小部件提供的spin回调函数在每次旋转时都会被调用。如果我们使用传统的旋转minmax选项,我们就永远不会知道是否出现了溢出,因为min会小于1,而max永远不会大于100。因此,我们使用新的选项根据方向重定向值。如果值超过了100,那么我们将值设置回minOverflow。或者如果值低于1,那么我们将值设置为maxOverflow

还有更多...

你可能会决定,当我们将用户带到旋转器边界的另一侧时,溢出行为并不完全符合你的期望。你可能只想在达到边界时停止旋转。然而,我们仍然可以通过禁用旋转按钮来改进小部件。这只是对旋转器溢出的另一种方法,我们只是为用户提供更好的反馈,而不是像之前那样改变业务逻辑。让我们看看如何做出这个改变。以下是用于简单旋转器小部件的 HTML 结构:

<div class="spinner-container">
    <input id="spin" value=10 />
</div>

这是我们在页面加载时用到的 JavaScript,用于创建小部件。

$(function() {

    $( "#spin" ).spinner({
        min: 1,
        max: 100,
        spin: function( e, ui ) {
            var value = ui.value,
                buttons = $( this ).data( "uiSpinner" ).buttons,
                min = $( this ).spinner( "option", "min" ),
                max = $( this ).spinner( "option", "max" );

            if ( value === max ) {
                buttons.filter( ".ui-spinner-up:not
                  (.ui-state-disabled)" )
                       .button( "disable" );
            }
            else if ( value === min ) {
                buttons.filter( ".ui-spinner-down:not
                  (.ui-state-disabled)" )
                       .button( "disable" );
            }
            else {
                buttons.filter( ".ui-state-disabled" )
                .button( "enable" );
            }
        }
    });

});

当你在浏览器中开始与这个小部件交互时,你会注意到当你达到min选项值时,即1,下旋转按钮会被禁用。

还有更多...

同样,当你达到了max,这里是100,上旋转按钮会被禁用。

还有更多...

通过向构造函数传递一个spin回调函数,我们引入了这种新的旋转器行为,该函数在每次旋转时执行。在这个回调中,我们将两个旋转按钮的引用都保存在buttons变量中。然后我们检查是否达到了max值,或者达到了min值。然后我们禁用适当的按钮。如果我们处于minmax之间,那么我们就简单地启用这些按钮。你还会注意到我们在这里有一些额外的过滤;not(.ui-state-disabled).ui-state-disabled。这是必要的,因为旋转器小部件触发旋转事件的方式。禁用按钮可能会触发旋转,导致无限循环。因此,我们必须小心地只禁用那些尚未被禁用的按钮。

简化旋转器按钮

spinner 小部件中实现的默认旋转按钮可能有点过多,具体取决于上下文。例如,您可以清楚地看到这些是作为子组件添加到滑块中的按钮小部件。当我们开始使用较小的小部件构建较大的小部件时,这完全有效。这更多地是一种审美偏好。也许如果单独的向上和向下旋转按钮没有悬停状态,也没有背景或边框,那么 spinner 会看起来更好。让我们尝试从滑块按钮中去除这些样式属性,并使它们看起来更紧密集成。

如何做...

这是作为我们 spinner 小部件基础的基本 HTML 结构:

<div class="spinner-container">
    <input id="spin" />
</div>

这是我们将使用的 CSS,用于移除我们不再感兴趣的按钮样式:

.ui-spinner-basic > a.ui-button {
    border: none;
    background: none;
    cursor: pointer;
}

input 元素尚未成为一个小部件,而我们创建的新 CSS 类也尚未成为 spinner 小部件的一部分。以下是完成这两件事情的 JavaScript 代码的样子:

 (function( $, undefined ) {

$.widget( "ab.spinner", $.ui.spinner, {

    options: {
        basic: false
    },

    _create: function() {

        this._super();

        if ( this.options.basic ) {
            this.uiSpinner.addClass( "ui-spinner-basic" );
        }

    }

});

})( jQuery );

$(function() {

    $( "#spin" ).spinner({
        basic: true
    });

});

如果您在浏览器中查看我们创建的 spinner,您会注意到 spinner 按钮的边框和背景已经被去除。现在它看起来更像一个整体小部件。您还会注意到,当用户将鼠标悬停在任一按钮上时,鼠标指针使用指针图标,这有助于表明它们是可点击的。

如何做...

工作原理...

我们刚刚创建的新 CSS 类 ui-spinner-basic 通过在 spinner 上下文中覆盖按钮小部件样式来工作。具体来说,我们从按钮小部件中移除了 borderbackground。此外,我们将 cursor 属性设置为 pointer,以便给用户一种箭头是可点击的印象。我们还稍微定制了 spinner 小部件本身的定义。我们通过添加一个新的 basic 选项来实现这一点,当 true 时,将新的 ui-spinner-basic 类应用于小部件。当小部件被销毁时,我们不需要显式地移除此类,因为它被添加到 spinner 小部件创建的一个元素中。此元素会被基本 spinner 实现自动移除,因此我们的代码不必担心它。