这个控件的网页在jQuery UI的官网:http://jqueryui.com/demos/slider/#range 可以找到。
在我最近正在阅读的《jQuery in Action》里,第十一章内有一个小节是用来介绍这个控件的,其中作者还提供了一个非常有用的实验程序:
这个程序运行良好,它的jQuery的版本是1.4,UI的版本是1.8。
问题描述:
这是基于官网的示例修改之后的一个简单演示(主题CSS有完全重新定制):
官网的演示引用的jQuery的版本是1.7.1:
所以,如果我在仍然引用最新版本的jQuery的情况下,程序运行正常。但是因为我的项目使用的jQuery的版本是1.6.2,jQ UI的版本是1.8.16,所以我将演示引用的库降级到这两个版本,结果是第一个滑块不能够通过键盘操作。
于是我用《jQuery in Action》中的示例做测试,将它的jQuery的版本分别换成1.6.2和1.7.1,结果是在1.6.2下滑块也出现同样问题。有趣的是我将官网演示的引用换成1.4版本,居然也运行正常,所以结论貌似是1.6.2的版本有些问题。
是否需要在1.6.2版本下修复滑块的问题?
这取决于整个网站的功能在升级到1.7.1之后,会不会有异常,而现在还无法判断。
定制該UI:
既然上一個問題暫時擱置,那就只需要來關注下怎麼修改這個演示,來進一步定制這個UI。
需求:
首先我要表達的數據是時間,以分鐘為最小單位,這個slider將用來表達一個時間上的區間;這需要一些在整數和時間格式之間的轉換。
另外,我需要將展示滑塊數據的input控件拆成四個,分別表示:起始小時,起始分鐘,終止小時,終止分鐘。
我總共需要三個這樣的slider控件。
實際操作:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Slider - Range slider</title>
<link rel="stylesheet" href="css/custom-theme/jquery-ui-1.8.18.custom.css">
<script src="script/jquery-1.7.1.min.js"></script>
<script src="script/jquery-ui-1.8.18.custom.min.js"></script>
<style>
#demo-frame > div.demo { padding: 10px !important; };
</style>
<script>
$(function() {
$( "#slider-period-1" ).slider({
range: true,
min: 465,
max: 1320,
values: [ 650, 1300 ],
slide: function( event, ui ) {
var start_hor = Math.floor(ui.values[ 0 ] / 60);
var start_min = (ui.values[ 0 ] % 60)>=10 ? (ui.values[ 0 ] % 60) : "0"+(ui.values[ 0 ] % 60);
var end_hor = Math.floor(ui.values[ 1 ] / 60);
var end_min = (ui.values[ 1 ] % 60)>=10 ? (ui.values[ 1 ] % 60) : "0"+(ui.values[ 1 ] % 60);
$( "#start_hour_prd-1" ).val( start_hor );
$( "#start_minute_prd-1" ).val( start_min );
$( "#end_hour_prd-1" ).val( end_hor );
$( "#end_minute_prd-1" ).val( end_min );
}
});
var init_start_min_str;
var init_end_min_str;
$( "#start_hour_prd-1" ).val( Math.floor( $( "#slider-period-1" ).slider( "values", 0 ) / 60) );
init_start_min_str = $( "#slider-period-1" ).slider( "values", 0 ) % 60;
if(init_start_min_str < 10)
{
init_start_min_str = '0' + init_start_min_str;
}
$( "#start_minute_prd-1" ).val( init_start_min_str );
$( "#end_hour_prd-1" ).val( Math.floor( $( "#slider-period-1" ).slider( "values", 1 ) / 60) );
init_end_min_str = $( "#slider-period-1" ).slider( "values", 1 ) % 60;
if(init_end_min_str < 10)
{
init_end_min_str = '0' + init_end_min_str;
}
$( "#end_minute_prd-1" ).val( init_end_min_str );
});
</script>
</head>
<body>
<div class="demo" id="period-1" style="margin-bottom:66px;">
<p>
<label>第一廣告時段:</label>
<input type="text" maxlength="2" size="2" id="start_hour_prd-1" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="start_minute_prd-1" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
--
<input type="text" maxlength="2" size="2" id="end_hour_prd-1" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="end_minute_prd-1" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
</p>
<div style="width: 600px; height: 22px;"><span style="float:left;">7:45</span> <span style="float:right;">22:00</span></div>
<div id="slider-period-1" style="width: 600px;"></div>
<div></div>
</div><!-- End demo -->
</body>
</html>
考慮到長遠的擴展,給每個HTML標籤命名ID的時候,都加上“-1”編號,來與之後加的標籤區分。
你可以通過給將要生成slider的DIV標籤加上寬度style="width: 600px;",來控制它的寬。
首先,slider的上下限的範圍是465~1320,這個數字是通過起止時間7:45~22:00換算成分鐘計算得來的,在滑塊的構造函數中,我們綁定了slider事件的處理函數,在這個函數中,通過將這個分鐘換算成時間再更新到四個input控件中去。
進一步修改:
現在input控件變回了它們本來的模樣,所以用戶會自然要在裏面直接輸入數字,而假如數字更新之後,滑塊的位置卻沒有相應地變化,用戶會覺得不好用甚至被誤解。所以需要給四個input加上change事件的處理函數,下面以起始小時的input控件為例:
$( "#start_hour_prd-1" ).change(
function(e)
{
var start_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-1" ).val() );
if( (start_time>450)&&(start_time<$( "#slider-period-1" ).slider("values", 1)) )
{
$( "#slider-period-1" ).slider("values", 0, start_time);
}
else
{
$(this).val('7');
$( "#start_minute_prd-1" ).val('40')
var def_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-1" ).val() );
$( "#slider-period-1" ).slider("values", 0, def_time);
}
}
);
這裡面可見,slider的構建函數即可以用來更新其滑塊的位置,另外注意,這裡加了一些數據有效性的驗證,因為用戶輸入的數據可能是在slider的範圍之外,當這種情況發生時,我們指定一個默認值。另外的三個input控件的做法類似。
提高靈活性:
因為之前提過,將需要三個slider,每個slider我們都需要給五個控件執行類似事件綁定的操作,加起來是十五個,我們將需要不停重複類似上面的Js函數太多次!
可是,觀察一下,每個slider都有與之關聯的四個input,可以讓它們使用相同規則的命名,最後加上不同的編號以示區分,但每個控件都與另外兩個的操作邏輯相同,比如三個起始小時input控件。
所以,這需要在函數內,我們不是直接通過硬編碼元素ID來尋找它,而是用一種相對的做法,比如在$( "#start_hour_prd-1" ).change()函數體中,我們不再直接引用#start_minute_prd-1或#slider-period-1,而是先把編號“1”從自身的ID中截取出來,附加在一個ID之後:
var cordial_str = $(this).attr("id").substr(-1, 1);
於是上面的操作就變成了:
$( "#start_hour_prd-1" ).change(
function(e)
{
var cordial_str = $(this).attr("id").substr(-1, 1);
var start_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-" + cordial_str ).val() );
if( (start_time>450)&&(start_time<$( "#slider-period-" + cordial_str ).slider("values", 1)) )
{
$( "#slider-period-" + cordial_str ).slider("values", 0, start_time);
}
else
{
$(this).val('7');
$( "#start_minute_prd-" + cordial_str ).val('40')
var def_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-" + cordial_str ).val() );
$( "#slider-period-" + cordial_str ).slider("values", 0, def_time);
}
}
);
但是這個做法有一個兼容性上的問題,它在IE下不能正常運作,通過google,發現是因為在IE下,對於substr的第一個參數為負值情況的處理是將整個字符串返回:
所以不得不另求他法,通過charAt()函數:
http://www.w3schools.com/jsref/jsref_charat.asp
修改后:
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
那麼,現在,我們已經可以用一個通用的函數來給input控件綁定change事件了;同時,在創建slider的函數中的slider事件函數,也可以使用同樣的思路將其泛化。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Slider - Range slider</title>
<link rel="stylesheet" href="css/custom-theme/jquery-ui-1.8.18.custom.css">
<script src="script/jquery-1.7.1.min.js"></script>
<script src="script/jquery-ui-1.8.18.custom.min.js"></script>
<style>
#demo-frame > div.demo { padding: 10px !important; };
</style>
<script>
$(function() {
var periods = new Array();
periods[0] = [530, 600];
periods[1] = [700, 730];
periods[2] = [1040, 1110];
/* create the sliders */
$( "#slider-period-1, #slider-period-2, #slider-period-3" ).each(
// function run on each element, be passed with two param,
// but we never need them, the context is the element itself
function( intIndex )
{
// on the current element, create a slider
$( this ).slider({
range: true,
min: 465,
max: 1320,
values: periods[intIndex],
// bind a handler for slide event
slide: function( event, ui ) {
var start_hor = Math.floor(ui.values[ 0 ] / 60);
var start_min = (ui.values[ 0 ] % 60)>=10 ? (ui.values[ 0 ] % 60) : "0"+(ui.values[ 0 ] % 60);
var end_hor = Math.floor(ui.values[ 1 ] / 60);
var end_min = (ui.values[ 1 ] % 60)>=10 ? (ui.values[ 1 ] % 60) : "0"+(ui.values[ 1 ] % 60);
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
$( "#start_hour_prd-" + cordial_str ).val( start_hor );
$( "#start_minute_prd-" + cordial_str ).val( start_min );
$( "#end_hour_prd-" + cordial_str ).val( end_hor );
$( "#end_minute_prd-" + cordial_str ).val( end_min );
}
});
}
);
var init_start_min_str;
var init_end_min_str;
/* initiate the inputs */
$( "#start_hour_prd-1, #start_hour_prd-2, #start_hour_prd-3" ).each
(
function( intIndex )
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
$(this).val( Math.floor( $( "#slider-period-" + cordial_str ).slider( "values", 0 ) / 60) );
}
);
$( "#start_minute_prd-1, #start_minute_prd-2, #start_minute_prd-3" ).each
(
function( intIndex )
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
init_start_min_str = $( "#slider-period-" + cordial_str ).slider( "values", 0 ) % 60;
if(init_start_min_str < 10)
{
init_start_min_str = '0' + init_start_min_str;
}
$(this).val( init_start_min_str );
}
);
$( "#end_hour_prd-1, #end_hour_prd-2, #end_hour_prd-3" ).each
(
function( intIndex )
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
$(this).val( Math.floor( $( "#slider-period-" + cordial_str ).slider( "values", 1 ) / 60) );
}
);
$( "#end_minute_prd-1, #end_minute_prd-2, #end_minute_prd-3" ).each(
function( intIndex )
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
init_end_min_str = $( "#slider-period-" + cordial_str ).slider( "values", 1 ) % 60;
if(init_end_min_str < 10)
{
init_end_min_str = '0' + init_end_min_str;
}
$(this).val( init_end_min_str );
}
);
$( "#start_hour_prd-1, #start_hour_prd-2, #start_hour_prd-3" ).each(
/*
* function run on each element, be passed with two params:
* index and the element, but we don't need them for now,
* the context is the element itself, that is enough
*/
function( intIndex )
{
// on the current element, bind a handler for change event
$( this ).change(
function(e)
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
var start_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-" + cordial_str ).val() );
if( (start_time>450)&&(start_time<$( "#slider-period-" + cordial_str ).slider("values", 1)) )
{
$( "#slider-period-" + cordial_str ).slider("values", 0, start_time);
}
else
{
$(this).val('7');
$( "#start_minute_prd-" + cordial_str ).val('40')
var def_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#start_minute_prd-" + cordial_str ).val() );
$( "#slider-period-" + cordial_str ).slider("values", 0, def_time);
}
}
);
}
);
$( "#start_minute_prd-1, #start_minute_prd-2, #start_minute_prd-3" ).each(
function( intIndex )
{
$( this ).change(
function(e)
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
var start_time = parseInt( $( "#start_hour_prd-" + cordial_str ).val() ) * 60 + parseInt( $(this).val() );
if( (start_time>450)&&(start_time<$( "#slider-period-" + cordial_str ).slider("values", 1)) )
{
$( "#slider-period-" + cordial_str ).slider("values", 0, start_time);
}
else
{
$( "#start_hour_prd-" + cordial_str ).val('7');
$(this).val('40');
var def_time = parseInt( $( "#start_hour_prd-" + cordial_str ).val() ) * 60 + parseInt( $(this).val() );
$( "#slider-period-" + cordial_str ).slider("values", 0, def_time);
}
}
);
}
);
$( "#end_hour_prd-1, #end_hour_prd-2, #end_hour_prd-3" ).each(
/*
* function run on each element, be passed with two params:
* index and the element, but we don't need them for now,
* the context is the element itself, that is enough
*/
function( intIndex )
{
// on the current element, bind a handler for change event
$( this ).change(
function(e)
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
var end_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#end_minute_prd-" + cordial_str ).val() );
if( (end_time<1380)&&(end_time>$( "#slider-period-" + cordial_str ).slider("values", 0)) )
{
$( "#slider-period-" + cordial_str ).slider("values", 1, end_time);
}
else
{
$(this).val('22');
$( "#end_minute_prd-" + cordial_str ).val('00')
var def_time = parseInt( $(this).val() ) * 60 + parseInt( $( "#end_minute_prd-" + cordial_str ).val() );
$( "#slider-period-" + cordial_str ).slider("values", 1, def_time);
}
}
);
}
);
$( "#end_minute_prd-1, #end_minute_prd-2, #end_minute_prd-3" ).each(
function( intIndex )
{
$( this ).change(
function(e)
{
// for IE compatblity
var id = $(this).attr("id");
var cordial_str = id.charAt(id.length-1);
var end_time = parseInt( $( "#end_hour_prd-" + cordial_str ).val() ) * 60 + parseInt( $(this).val() );
if( (end_time<1380)&&(end_time>$( "#slider-period-" + cordial_str ).slider("values", 0)) )
{
$( "#slider-period-" + cordial_str ).slider("values", 1, end_time);
}
else
{
$( "#end_hour_prd-" + cordial_str ).val('22');
$(this).val('00');
var def_time = parseInt( $( "#end_hour_prd-" + cordial_str ).val() ) * 60 + parseInt( $(this).val() );
$( "#slider-period-" + cordial_str ).slider("values", 1, def_time);
}
}
);
}
);
});
</script>
</head>
<body>
<div class="demo" id="period-1" style="margin-bottom:66px;">
<p>
<label>第一廣告時段:</label>
<input type="text" maxlength="2" size="2" id="start_hour_prd-1" name="start_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="start_minute_prd-1" name="start_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
--
<input type="text" maxlength="2" size="2" id="end_hour_prd-1" name="end_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="end_minute_prd-1" name="end_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
</p>
<div style="width: 600px; height: 22px;"><span style="float:left;">7:45</span> <span style="float:right;">22:00</span></div>
<div id="slider-period-1" style="width: 600px;"></div>
<div></div>
</div><!-- End demo -->
<div class="demo" id="period-2" style="margin-bottom:66px;">
<p>
<label>第二廣告時段:</label>
<input type="text" maxlength="2" size="2" id="start_hour_prd-2" name="start_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="start_minute_prd-2" name="start_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
--
<input type="text" maxlength="2" size="2" id="end_hour_prd-2" name="end_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="end_minute_prd-2" name="end_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
</p>
<div style="width: 600px; height: 22px;"><span style="float:left;">7:45</span> <span style="float:right;">22:00</span></div>
<div id="slider-period-2" style="width: 600px;"></div>
<div></div>
</div><!-- End demo -->
<div class="demo" id="period-3">
<p>
<label>第三廣告時段:</label>
<input type="text" maxlength="2" size="2" id="start_hour_prd-3" name="start_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="start_minute_prd-3" name="start_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
--
<input type="text" maxlength="2" size="2" id="end_hour_prd-3" name="end_hour[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
:
<input type="text" maxlength="2" size="2" id="end_minute_prd-3" name="end_minute[]" style="border:1; color:#f6931f; font-weight:bold; width:20px;" />
</p>
<div style="width: 600px; height: 22px;"><span style="float:left;">7:45</span> <span style="float:right;">22:00</span></div>
<div id="slider-period-3" style="width: 600px;"></div>
<div></div>
</div><!-- End demo -->
</body>
</html>
按照jQuery的官方文檔,each()函數接收的參數是一個匿名函數,它會在wrapper set中的每個元素上執行,在執行的時候,有兩個參數會被傳給這個匿名函數,其中第一個就是當下元素在集合中的索引值,所以它的次序一定是從0開始依次遞增的整數;上面的代碼中利用了這個參數,它首先建立了一個數組,之後利用這個參數訪問數組的不同元素,這樣就做到了可以令每個slider的初始值不同。另外,在通過each()而被執行的匿名函數裡,它的this指向的是當下的元素。
關於jQuery里的this:http://remysharp.com/2007/04/12/jquerys-this-demystified/
在這個文件的script區塊里,當指定DOM加載完畢后執行的動作時:我們一共呼叫了九次函數表達式,但是如果不使用泛化的設計,將會變成是二十七次,而且將隨著slider控件增多而繼續增多。
這個頁面在IE,Firefox與Chrome下都正常運行。
傳遞到服務器:
當然,做這樣一個用戶友好介面的最終目的還是讓它的數據可以傳回到服務器端。現在的問題是以何種形式可以高效地把數據表達并傳給服務器端的PHP程序。
每個slider上面的數字已經是input控件了,只需將它們放在一個form里,就可以很簡單地傳回服務器了,當然在此之前需要給這些控件指定name屬性,既然很多控件是相似的,那麼不如就按照這一點將不同的控件用一個數組名的形式來命名。比如將所有ID以“start_hour_prd”開頭的input的name屬性設置為“start_hour[]”,於是在PHP那邊,就會得到一個名為“start_hour”的數組,它有三個元素。
<?php
$start_hour = $_POST['start_hour'];
$start_minute = $_POST['start_minute'];
$end_hour = $_POST['end_hour'];
$end_minute = $_POST['end_minute'];
for($i=0; $i<count($start_hour); $i++)
{
echo "第" . ($i+1) . "廣告時間段:" . $start_hour[$i] . ":" . $start_minute[$i] . "至" . $end_hour[$i] . ":" . $end_minute[$i]. "<br>\n";
}
?>
提交後: