January 2009 - 文章

jQuery 1.3自訂Selector Filter注意事項

上回介紹過自訂jQuery Selector Filter的做法,在範例中判斷true/false的邏輯用的是字串表示法,例如:
$.extend($.expr[":"], { exact: "(a.textContent||a.innerText||jQuery(a).text()||'') == m[3];"  });

不過,這個簡便字串表示法到了jQuery-1.3就不管用了,會導致Line: 1938 Error: Function expected的錯誤。追了一下原始碼,發現在1.2.6中有以下的邏輯,當發現設定的Filter是字串時,會透過eval方式將其轉換成Function。

            // Otherwise, find the expression to execute
            } else {
                var fn = jQuery.expr[ m[1] ];
                if ( typeof fn == "object" )
                    fn = fn[ m[2] ];
 
                if ( typeof fn == "string" )
                    fn = eval("false||function(a,i){return " + fn + ";}");
 
                // Execute it against the current filter
                r = jQuery.grep( r, function(elem, i){
                    return fn(elem, i, m, r);
                }, not );
            }

在1.3版中,Selector已改為全新的Sizzle引擎,邏輯重寫過,甚至jQuery.expr[":"]都是另行對應宣告過才做到向前相容:

// EXPOSE
jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;

目前看來,Sizzle裡已不再提供Filter表示字串自動轉函數的功能,因此上述的例子,要改回用標準函數宣告才可在jQuery 1.3+中正確執行:
$.extend($.expr[":"], { exact: function(a, i, m) { return (a.textContent || a.innerText || jQuery(a).text() || '') == m[3]; } });

ReportViewer! RDLC!

前些時候才發現我忽略多時的好物一枚--ReportViewer及RDLC!

之前在SSRS上開發過幾張具備機密性的查詢報表,檢視報表前需先通過額外的身份識別關卡,並要管控每個人可視資料範圍不同,這些用程式搞定是小事一椿,但邏輯搬到SSRS後處處受限,七拼八湊才搞出個堪用但頗為複雜的解決方案。當得知RDLC可以不靠SSRS,只要設法自己生一個DataTable就能享有SSRS報表的相同便利性時,"相見恨晚"的懊悔又油然而生...

http://www.gotreportviewer.com/有一篇很好的FAQ介紹RDLC,去信取得原作者授權後,我翻譯改寫如下:

Q: ReportViewer 2008有什麼新功能?
A: 這篇Blog裡有ReportViewer 2008的新功能清單。

Q: ReportViewer 2008是否與Reporting Services 2008的新功能相容? (如Tablix)
A: 不行耶。Visual Studio 2008比SQL 2008的誕生早了好一陣子,所以ReportViewer 2008是以SSRS 2005 RDL為基礎設計的,ReportViewer應會在下次更新時加入RDL 2008的新功能。

Q: ReportViewer控制項是否被包含在SQL Server 2005/2008中?
A: 不不不,Report Server是SQL Server 2005/2008家族的成員, 但ReportViewer控制項不是。ReportViewer被包含在Visual Studio 2005/2008中。

Q: 要使用ReportViewer控制項需要安裝SQL Server嗎?
A: 嘿,不用,只要有.NET Framework 2.0就搞定了。

Q: 要使用ReportViewer控制項需要具有SQL Server授權嗎?
A: 哇哈哈,不用! ReportViewer控制項可以自由散佈,在本機模式(local mode)下不需任何SQL Server,啟用遠端模式(remote mode)時,則需要連上Report Server,而Report Server需要有SQL Server授權才能安裝使用。

Q: 報表的資料來源必須來自SQL Server資料庫嗎?
A: 報表的資料來源沒有任何限制,但與Reporting Service RDL不同的是,查詢資料的工作必須由應用程式自行完成,並以ADO.NET DataTables或Business Object集合的形式提供給ReportViewer,ReportViewer控制項完全不管你的資料是從哪裡生出來的。

Q: ReportViewer可以檢視SQL Server 2000 Report Server上的報表嗎?
A: 不行,在遠端模式(remote mote)下,ReportViewer控制項只能檢視SQL Server 2005 Report Server(SSRS)上的報表,但報表的資料來源則無限制,SQL Server 2000, Oracle, DB2... 皆可。

Q: ReportViewer控制項是.NET Framework的一部分嗎?
A: 並不是,ReportViewer控制項隨Visual Studio 2008發行,而非.NET Framework的一部分。

Q: 哪些版本的Visual Studio 2008包含ReportViewer?
A: 標準版以上都有(換句話說,只有Visual Web Developer Express沒有,但有其他解決方案)。

Q: RDL與RDLC格式的差別在哪裡?
A: RDL檔案是SQL Server 2005 Report Designer的產出結果;RDLC 檔案則是由Visual Studio 2008 Report Designer設計所得。
RDL與RDLC有相同的XML Schema,但RDLC中部分值(如Query Text)允許空白,因此RDLC無法直接發佈到SSRS上,除非用SQL Server 2005 Report Designer編輯並補上缺少的值。
RDL則完全相容於ReportViewer控制項,但是RDL少了一些讓設計階段產生自動Data-Binding的資訊,要在ReportViewer中使用,得加上些手工
注意,ReportViewer控制項不包含任何連線資料庫或執行查詢的邏輯,完全要由程式自行打理,甚至可以從非資料庫來源組出報表所需的資料。因此,RDL中的資料庫連線及查詢語句將會被忽略,不要期望ReportViewer會幫你連DB查資料,一切得自己寫程式處理,把報表所需的ADO.NET DataTable生出來。(這點是限制也是優點)

Q: RDLC的C是啥意思?
A: C代表Client-side processing. RDL則是Report Definition Language.

Q: 為何ReportViewer控制項在本機模式(local mode)下不支援參數提示與輸入功能?
A: 參數提示與輸入功能只有在遠端模式提供,理由是本機模式下,ReportViewer並不會主動執行查詢,只被動地接收DataTable成果,既然這些工作都由應用程式處理,ReportViewer無從亦不宜插手。

Q: 我安裝了Visual Studio 2008及Report Designer,卻找不到預覽(Preview)頁籤。SQL Server 2000 Report Designer裡那個預覽頁籤到哪裡去了?
A: 斯斯Report Designer有兩種,一種是Visual Studio 2008提供的,另一種則來自SQL Server 2005 box。預覽功能只有SQL 2005版的有,VS2008版的則要透過執行程式才會看到結果。理由很簡單,查詢資料的邏輯寫在程式碼裡,不把程式跑起來,就無從取得資料,何來預覽?

Q: 為什麼不能給個RDL就看到報表?
A: 遠端模式下,可以給個URL就直接看到報表。本機模式下,報表的資料來源要靠程式生,因此你必須在程式裡包辦開啟連線、查資料、取得DataTable、關閉連線等一連串工作,不過,這也賦與程式更多的設計彈性(想像一下,能將AD、XML、DB的結果整合成一個DataTable有多酷)。
不過別怕,靠著Visual Studio的Data Wizard以及ADO.NET DataAdpate元件,查詢資料一點都不難。

Q: 我從資料來源(Data Sources)視窗拖了一個欄位到報表中,但執行程式時只看到一列,要如何看到所有資料?
A: 要看到所有資料列,先拖一個Table或List到報表上,再拖欄位到Table或List上。

Q: 使用WebForms ReportViewer控制項時,先顯示"Report is being generated"訊息,但訊息一消失,報表卻連鬼影子都沒看到。
A: 如果你將ReportViewer的高度設為百分比,請刪除.aspx的XHTML DOCTYPE設定。說明

Q: 要使用xls匯出功能,必須先安裝Excel嗎?
A: 免,xls匯出功能並不依賴Excel,檢視時才需要。

Q: 使用PDF匯出功能需要取得Adobe授權嗎?
A: 不用,ReportViewer控制項並未使用到Adobe的技術產生PDF,PDF是公開標準,任何人都不需授權就可以產生PDF檔案。

Q: ReportViewer控制項有什麼功能限制?
A: 與Report Server相比,ReportViewer控制項不會自己連上DB及執行查詢,在本機模式下只能匯出Excel及PDF(遠端模式下則SSRS的全部格式都支援),ReportViewer控制項不支援自訂Renderer及Report Item。
SSRS除了匯出格式較多,還具有效能擴充性、集中儲存、統一管理、快取、訂閱等額外功能。

Web Site Project vs Web Application Project

網友saintchou問了一個問題,他的VS2005 SP1網站發行功能選項跟KB-ASP.NET 2.0 網站部署的變革一文所說的不同(該文留言中有選項畫面圖檔連結),有了圖,答案揭曉: saintchou使用的Web Application Project,與文中討論部署有點棘手的Web Site Project是兩種不同專案類型,發佈網站的選項因此不同。

為什麼Visual Studio中會冒出兩種網站專案類型? 這問題還挺有趣的,且扯到一點歷史,索性再做點補充。

詳細的故事源由在KB-ASP.NET 2.0 網站部署的變革文章-ASP.NET 2.0專案部署問題研究兩篇文章裡提到蠻多的,這裡再強調一下如何在Visual Studio 2005 SP1/Visual Studio 2008裡建立及區別Web Site Project與Web Applicatoin Project。

VS2005在一開始時只有Web Site Project模式(Code-Beside),雖然它有架構簡化、修改更新簡便等特性,但不少開發者適應不良,還是懷念ASP.NET 1.1那種需要先Build DLL才能執行的模式,於是微軟先推出了需要額外安裝的Web Application Project套件,讓開發者可以在VS2005中繼續用ASP.NET 1.1的方式寫網站。等到VS2005 SP1推出時,便將Web Application Project功能改為內建,但對於來不及參與這段過去的人來說,Visual Studio 2005裡"斯斯網站有兩種"變成一件奧妙的事。(Visual Studio 2008裡也同時支援這兩種網站專案)

Web Site Project的建立與開啟方式與Web Application Project不同,Web Site Project要由File/New/Web Site來新增,Web Application Project則是要由File/New/Project,再選擇ASP.NET Web Application專案類型(畫面範例看這裡)。

兩種Web Model要挑哪一種各憑所好(MSDN裡有份二者的比較,還包含了特性對照表,可以做為參考),開發時明確知道你採用的是哪一種專案類型即可。

PS: 我個人偏好Web Site模式,在測試開發階段只要改.cs就可以直接看結果,只要Copy檔案就能執行的特性也很方便;至於要部署到正式環境時,則可借助Web Deployment Project將程式碼預先編譯成DLL,避免原始程式碼外洩的疑慮。

jQuery Selector不合用? 自己寫一個吧!

先前文章的留言討論中,引出了一個有趣話題。

jQuery的Selector種類繁多,神奇又好用,但在某些情境下還是可能找不到100%符合需求的。像是:contains()可以用來比對"文字內容包含某個字串",但如果我們要的是"文字內容完全等於某個字串"或是"文字內容完全等於某個字串,但不用分大小寫",要怎麼辦?

先假設情境是要找出所有文字內容恰等於"Darkthread"的<a>,例如: <a href="..." mce_href="...">Darkthread</a>,如果用$("a:contains('Darkthread')")則連<a href="..." mce_href="...">Fake Darkthread</a>也會被算進來。

一個簡單且直覺的方法是利用filter函數:
$("a").filter(function() { return $(this).text() == "Darkthread" });

但有更巧妙的做法--Selector不合用? 那就自己寫一個! jQuery在架構上已預留可自行擴充Selector的空間,要擴充很簡單。說到這裡,大家對jQuery的景仰是否已如濤濤江水?

假設我們對:contains()的"包含"特性不滿意,我們打算生出另一個:exact('...')的Selector,限定文字內容要完全相符才算數,只需要一行Code:
$.extend($.expr[":"], { exact: "(a.textContent||a.innerText||jQuery(a).text()||'') == m[3];"  });

接著就可以用$("a:exact('Darkthread')")來找出文字完全吻合Darkthread的<a>了。

回頭來看一下要自訂Selector的設定語法
$.extend($.expr[":"], { filterName: filterEvalExpression });

其中filterName是要自訂的Selector Filter名稱,filterEvalExpression的字串內容則是一個邏輯運算式,用以傳回true或false,而在運算式中,我們有幾個預設變數可用:

  • a 比對的DOM元素物件
  • i 元素在查詢結果的排序位置
  • m 拆解Selector而成的字串陣列,當Filter是:exact('aaa')時,可利用m[3]取得aaa,作為比對之用。

有興趣深入研究的人可以參考jquery.js裡的Filter寫法,應該很快就能照著研發出自己想要的Selector。

至於不分大小寫的比對,相信難不倒大家吧:
$.extend($.expr[":"], { exactCI: "(a.textContent||a.innerText||jQuery(a).text()||'').toLowerCase() == m[3].toLowerCase();"  });

【2009-01-31更新】jQuery 1.3中無法直接以字串表示過濾邏輯,需寫成$.extend($.expr[":"], { exact: function(a, i, m) { return (a.textContent || a.innerText || jQuery(a).text() || '') == m[3]; } }); 詳細說明

jQuery 1.3.1 Release

jQuery 1.3出來才七天,網友Ark傳來快報,jQuery 1.3.1問市了。

不過1.3->1.3.1只在於修復Bug,並無功能上的增刪,毋需大書特書。不過公告裡提到兩點值得一提:

  1. jQuery已掛上Google’s CDN,如果不想將jQuery部署在網站上或想分散流量,可以直接使用以下兩個連結: jQuery MinifiedjQuery Regular(即Uncompressed版),或使用Google AJAX Libraries API載入。(使用方式可以參考保哥的文章: 網站部署可考慮使用 Google AJAX Libraries API 載入 JS)
  2. 1.3版起已不再提供Packed版本(壓縮編碼版,網頁每次引用時都要解壓),理由是Packed版不利於Debug、且在某些平台上(如Adobe AIR)裡無法正常運作,最要命的是,檔案傳輸量變少的效率提升不敵每次都要重新解壓的損耗,搞到最後載入過程反而最慢。看來它唯一的優點是節省Web Hosting的頻寬成本,但現在已可靠Google CDN解決,連最後的存在價值也消失殆盡。所以囉... 再會了,Packed版。
邊做邊學jQuery教學系列,初登場

一切起源於微軟正式在產品中採用了jQuery...

話說去年底某一天,微軟的魔力長官忽然找上中年人,問有沒有辦法製作一系列介紹如何在微軟環境下應用jQuery開發的錄影教學短片及文章?當時不知為何鬼迷心竅、又或者是初牛之犢(誤... 快40歲的小牛?)不畏虎,加上與jQuery陷入熱戀被沖昏了頭,就這麼胡里胡塗地一口答應下來。

平日常在不少場合看過講師們在台上口沫橫飛口若懸河,配合生動的展示範例將玄妙的技術娓娓道來,實際做一遭才知道什麼叫"台上十分鐘、台下十年功"。總之,實際上撰寫教學文章、設計範例、展示錄影比想像中困難許多,需要投入的時間也多得嚇人。尤其是錄影時得一氣喝成流暢地講解,明明平日講話碎碎念可以比美連珠砲,在家錄影又不像研討會需要面對上百觀眾,對著螢幕自言自語居然也常搞到腦中空白一片不知所云...

無論如何,【素人講師】很努力地在工作家庭之餘設法擠出時間,著手製作一系列"邊做邊學jQuery"教學,適合對象是"已具備粗淺Javascript基礎且想學習jQuery的開發人員",雖然品質水準不能與專業講師相提並論,大家偶爾看看清新脫俗青澀純樸的素人演出當作苦悶生活的調劑也不錯... (事到如今,只能這麼安慰自己了)

每篇文章都不長(患有資訊焦慮症的我看到文章太長就會放棄閱讀,算是將心比心)、展示錄影時間也短短的約5-10分鐘(再長周公就要到府上串門子了),大家可抱著輕鬆的心情看看。畢竟,"喂! 寫程式的,用jQuery是一件很快樂的事。"

要看到中年人的博命演出,大家可以在MSDN的邊做邊學系列影片網頁找到連結,另外在MSDN ASP.NET Developer Center的右方也有連結。目前已完成二集: (基於MSDN平台的限制,文章網頁原本用的SyntaxHighlighter顯示功能被換掉,程式碼的呈現效果大打折扣,請大家見諒)

有興趣的朋友可以連上去看看,並歡迎給予批評指教,有什麼建議可在此留言,我會虛心領教,並在後續的教學中盡力改善,謝謝大家。

(邊做邊學jQuery系列將有15集,這下可好,今年年假可充實了... 鳴~~~)

【茶包射手專欄】OracleClient LEFT JOIN時出現ORA-01405

小熊子的KB-當 ADO.NET 與 Oracle 問題集錦裡有個Tip,使用System.Data.OracleClient會產生ORA-01405: fetched column value is NULL,改用Oracle.DataAccess.Client就正常。

對這個問題做了點深入的研究,原本會產生錯誤的程式很龐雜,我將程式碼簡化但仍保有可產生ORA-01405的地步。

using System;
using System.IO;
using System.Threading;
using System.Data;
using System.Data.OracleClient;
//REFDLL System.Data.OracleClient;System.Data;System.Xml
 
public class CSharpLab
{
    public static void Test()
    {
        using (OracleConnection cn = new OracleConnection("Data Source=...."))
        {
            string strSQL =
@"
SELECT 
p.DRKCORPID, q.DRKQuoteTIME
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'
";
            OracleCommand cmd = new OracleCommand(strSQL, cn);
        
            
            cn.Open();
            try {
                OracleDataReader dr = cmd.ExecuteReader();
                dr.Read();
                Console.WriteLine(dr["DRKCORPID"]);
            } catch (Exception ex) {
                Console.WriteLine(ex.ToString());
            }
            cn.Close();
        }
    }
}

程式產生的錯誤訊息是:

System.Data.OracleClient.OracleException: ORA-01405: fetched column value is NULL

   at System.Data.OracleClient.OracleConnection.CheckError(OciErrorHandle errorHandle, Int32 rc)
   at System.Data.OracleClient.OracleDataReader.ReadInternal()
   at System.Data.OracleClient.OracleDataReader.Read()
   at CSharpLab.Test()

大致推斷的原因是這個LEFT JOIN在某些情況下,q.DRKQuoteTIME可能為NULL。我發現調整SQL的語法,會產生不同的結果,於是做了一連串有趣的測試。

多查詢兩個欄位(DRKPRODNAME, DRKProdType),執行成功。

SELECT
p.DRKCORPID, q.DRKQuoteTIME
,p.DRKPRODNAME, p.DRKprodtype
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'

將多查詢的兩個欄位移去,發生錯誤。

SELECT
p.DRKCORPID, q.DRKQuoteTIME
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'

限定前2731筆,執行成功。

SELECT
p.DRKCORPID, q.DRKQuoteTIME
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'
WHERE ROWNUM < 2731

多SELECT SYSDATE,發生錯誤。

SELECT
p.DRKCORPID, q.DRKQuoteTIME
, SYSDATE
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'
WHERE ROWNUM < 2731

去除SYSDATE,但限定改為前2732筆,執行成功。

SELECT
p.DRKCORPID, q.DRKQuoteTIME
FROM DRKPRODUCT p LEFT JOIN DRKQuote q
ON q.DRKPRODID=p.DRKPRODID AND p.DRKCORPID='6666'
WHERE ROWNUM <
2732

由這些線索看來,感覺上是查詢結果剛好組合成某種狀況時才會出錯,但其形成要件深藏在OracleClient元件中是個謎,但我認為這可歸究為元件的Bug!

要克服這個問題,除了改用ODP.NET之外,在可能產生NULL的欄位上加上NVL保護,例如: NVL(q.DRKQuoteTIME, '0000'),也不失為一個好的解決方法。(試了一下,若真的希望傳回NULL,可以寫成有點好笑的NVL(q.DRKQuoteTIME, NULL),也可避開這個Bug)

【茶包射手專欄】坎坷ODP.NET 10升級之路

有台Web,存取Oracle的程式不定期會出現以下錯誤:

  • [OracleException: ORA-01036: 變數名稱﹧號碼無效] Oracle.DataAccess.Client.OracleException.HandleErrorHelper(Int32 errCode, OracleConnection conn, IntPtr opsErrCtx, OpoSqlValCtx* pOpoSqlValCtx, Object src, String procedure) +751 Oracle.DataAccess.Client.OracleCommand.ExecuteReader(Boolean requery, Boolean fillRequest, CommandBehavior behavior) +2430 Oracle.DataAccess.Client.OracleCommand.ExecuteReader() +86
  • [IndexOutOfRangeException: 在結果集中找不到指定的資料欄] Oracle.DataAccess.Client.OracleDataReader.GetOrdinal(String name) +582

這兩個錯誤表面上是OracleParameter及SELECT欄位沒寫好,理應很好Debug,但巧妙之處在於平時程式都正常運作無誤,不定期才會發生上述錯誤,更難解釋的是,IISRESET後就網頁就可恢復正常好一陣子...

由以上線索看來,我推測問題跟ODP.NET有關,跟同事討論後,由於連線DB已是Oracle 10,而目前網站用的ODP.NET版本為9207,決定把ODP.NET升級到10換換手氣。

Oracle網站下載了ODAC1020221.exe,高高興興裝好,才發現程式仍繼續用ODP.NET 9207,爬了一下安裝說明,才發現原來它預設並不會幫你套用Policy強制改用新版ODP.NET。

解決方式是安裝$Oracle10.2Home$\ODP.NET\PublisherPolicy\1.x\Policy.9.2.Oracle.DataAccess.dll到GAC中。安裝Policy導向後,果然原本用ODP.NET 9207的網頁就自動改抓10.2版(為了區別起見,我故意將9.2的network/admin/tnsnames.ora搬走,如果錯用9.2,會出現ORA-12154 TNS無法解析服務ID的訊息)。

但麻煩事來了,原來從ODP.NET 10.2版起,Oracle.DataAccess.dll的版號不再沿用先前的9.*, 10.*而是分成.NET 1.1及.NET 2.0兩顆dll,版號分別為變1.102.2.20, 2.102.2.20,Policy.9.2.Oracle.DataAccess.dll會將網頁參考的ODP.NET 9207導成1.102.2.20版;要命的是網頁參考了另一顆元件MyWebControl,它也參考了ODP.NET 9207,你猜怎麼著,.NET Framework覺得MyWebControl原本參考的可是9.2版,哪有回頭去參考1.102版的道理(就算它比較新)?

CS1705: Assembly 'MyWebControl, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' uses 'Oracle.DataAccess, Version=9.2.0.700, Culture=neutral, PublicKeyToken=89b483f429c47342' which has a higher version than referenced assembly 'Oracle.DataAccess, Version=1.102.2.20, Culture=neutral, PublicKeyToken=89b483f429c47342'

不想去跟這問題拼命,也不想要求相關人員用新版ODP.NET Rebuild MyWebControl,研究了一下,發現改成1.*/2.*是10.2以後的事,ODP.NET 10.1還是遵循古法,採用10.1.0.4的編碼慣例,應可避開上述坑洞。於是移除ODP.NET 10.2,重裝ODP.NET 10.1,安裝程式自己會連Policy強制升版一併裝妥,IISRESET後,網頁終於順利跑將起來。

壞消息是,先前的OracleParameter鬼問題並沒有因為換了10.1而解決,看來還得繼續博鬥下去...

CODE-抛棄式Javascript匿名函數的使用

來試試用程式碼寫Blog...

//網友wangaguo詢問以下Javascript表示法的用意
(function(m) {
    alert(m);
})("Hello");
//以上的寫法相當於
var x = function(m) { alert(m); }
x("Hello");
//或是再傳統一點, 用具名函數來示範
function y(m) {
    alert(m);
}
y("Hello");
//以上三種寫法結果相同,差別在於第一種寫法不會留下任何函數或變數
//對於一次性的動作來說,可以做到真正的船過水無痕
//在先前"CODE - 動態依序載入JS"一文中也有提到
 
//至於在jQuery Plugin開發時,習慣上會寫成以下的格式
(function($) {
    //Plugin可以放心地用$代替jQuery
    //不必擔心網頁可能被設了jQuery.noConflict()
    //將$符號保留給其他Javascript Library使用
})(jQuery);
jQuery 1.3來了!!

jQuery於2009/01/14釋出1.3版!!

看了一下發行文件,這次的改版有幾大特色:

  • Selector引擎大換血:
    代號為Sizzle的新版引擎,比前一個版本1.2.6速度提高49%,跟Prototype、MooTools、Dojo相比,目前jQuery1.3是最快的。值得一提的是,Sizzle是個獨立Selector引擎,並非jQuery獨有,未來在其他AJAX Framework裡也可以看到它的身影。
  • Live Events:
    以往在使用jQuery .bind()時只能侷限於設定當下已存在的元素,而live()則可以包含未來!!
    例如: 當我們宣告在所有".clsClick"元素上用live()綁上click事件,意思是將來才出現的".clsClick"也將適用!! 因此如下例中,BBB是live()後才加上class="clsClick",一樣會對click有反應。live()省去了過去元素搬家或新增時總要反覆重bind的煩人工程,是一大突破。

<script type="text/javascript">
    $(function() {
        $(".clsClick").live("click", function() { alert("Clicked!"); });
        $("div").addClass("clsClick");
    });
</script>
<div class="clsClick">AAA</div>
<div>BBB</div>

  • jQuery.Event重構:
    jQuery的Event物件重新改寫過,加入更多W3C對事件物件的定義,當然,可跨瀏覽器使用。
  • HTML字串轉元素效率提升:
    jQuery裡"給HTML字串變元素"的特性(例如用於after(), append()時)十分好用,但效率不佳,常變成效能上的瓶頸,1.3版重新改寫後,速度提高六倍。另外,建立DOM物件如$("<script />")的速度現在跟$(document.createElement("script"))一樣快。
  • Offset重寫:
    求取元素座標的函數offset()重寫過了,排除了跨瀏覽器的問題,而且也快了三倍。
  • 不再使用Browser版本判別技術:
    為了解決跨瀏覽器的問題,傳統的解法會偵測瀏覽器版本,假設某一版本特性如何如何,再見招拆招。但如果瀏覽器改版或是修掉某個Bug,見招拆招的邏輯肯定就得再調整,不勝其擾。因此jQuery試圖用另一種概念,以偵測瀏覽器是否支援某個功能代替由瀏覽器版本推論其支援功能,如此能更貼近實務也更有彈性。新物件jQuery.support提供了瀏覽器是否支援某些特性的資訊,但jQuery.browser仍會持續存在。

另外,1.3版本有些更動是換版時必須修改程式配合的:

  • 如果還有Selector [@attr]的古早寫法,請將@移除
  • 使用jQuery觸發的事件,會沿著parents(),Bubble-Up到整個DOM Tree。如以下的例子: jquery-1.2.6.js時只會alert('dIn'),但若用的是jquery-1.3.js,則會依序看到dIn, dMid, dOut。
    <script type="text/javascript">
        $(function() {
            $("#TestZone div").click(function() { alert(this.id); });
            $("#dIn").click();
        });
    </script>
    <div id="TestZone">
    <div id="dOut"><div id='dMid'><div id="dIn">IN</div></div></div>
    </div>
  • jQuery.isFunction()基於效能及簡單化修改邏輯,內建函數(如alert)及DOM方法(如getAttribute())可能不被判定為函數。
  • $("a,b,c")傳回的結果改為一律依元素在文件中的順序,不再受a,b,c排列的影響。
  • trigger/triggerHandler不再接受放在陣列中的event物件,請改為直接傳入。另外,未寫在文件中的函數extra已刪除。
  • jQuery.event.trigger不再傳回Handler傳回的物件,而改為傳回true/false。
  • 請使用Standards Mode檢視使用jQuery的網頁,在Quirks Mode時可能會有問題。

最後再整理一下1.3版的一些補充特色:

  • Selector :not()支援複合式語法,例如: :not(a,b), :not(div a)現在都可以用了。
  • .toggleClass("className", true/false); 可以依條件設定或取消。
  • .closest( selector )在父系元素中找到最近的特定元素,例如: 找到包住元素的最內層tr,過去我常寫成.parents("tr:first"),現在可以直接用.closest("tr")
  • .is()支援複合式語法,例如: .is("div a")
  • .show()/.hide()改寫過,加快2.5倍。
  • show/hide/toggle/slideDown/slideUp/slideToggle除了width, height, opacity變化外,又多加了margin與padding,動畫效果也較為順暢。
  • .toggle(true/false)依條件決定show/hide,就是之前有篇文章討論條件化show/hide時所肖想的功能,現在內建了。
  • jQuery.fx.off可以用來關閉動畫過程特效,省略炫目的視覺效果讓網頁在效能不佳的裝置上能操作得更順暢一些。
  • Ajax settings中多了dataFilter callback可以用來過濾傳回內容,排除一些有害或無用的內容。
  • Ajax settings多了xhr可傳入自訂的XMLHttpRequest物件,方便開發者做進階操控。
  • Ajax/load傳參數給server時,原本只能給key/value,現也可直接給字串當參數
  • jQuery.isArray()新函數,可以用來檢測是否為陣列。

注意: 由於官方版的jquery-1.3-vsdoc.js還未釋出,因此在Visual Studio 2008中使用jQuery 1.3,Javascript Intellisense會有問題,不過不到24小時就已有強者釋出了解決方案,急用的人可以到Jamse的Blog下載jQuery 1.3 intellisense header file存成jquery-1.3-vsdoc.js頂著用。

CODE-動態依序載入JS

最近在翻修以前寫的控件(Web Control),其中有不少操作互動要靠Javascript處理。

當初還不懂jQuery(更精準點說,是jQuery還沒出生),乖乖用Javascript一行行把功能堆出來。用慣了jQuery後,這回要改寫翻新功能,說實在的,我已喪失直接用Javascript Hard-Code的耐性。就好像跟過大哥,體驗過拿手槍汽油彈在街頭火拼的爽快後,就很難再脫離幫派回去過撿石頭木棍跟人打架的生活。(這比喻一整個怪 >_<)

不過,我遇到了一個難題是,由於控件會被其他開發者引用,是屬於被支配、被指使的人頭角色,無法主控網頁的結構,因此要引用jQuery前必須確認網頁有include jquery.js。比較消極的做法是要求開發者在引用控件時一定要自行Include控件用到的幾個js,不然控件就出錯給你看以示薄懲。

我的野心稍微大一點,希望控件可以聰明地見機行事,若網頁已經載入必要的js,就不必多事,直接給它用下去就對了;否則控件要能自行載入所需的js,以供後續使用。

要實現這個理想,有三個重點: 1) 偵測必要的js是否已載入完成 2) 使用Javascript載入js 3) 多個js必須依序載入(例如: 先jquery.js再jquery.blockUI.js)。

針對1),我們可以檢測特定物件/函數是否存在(例如:typeof jQuery != "undefined")加以判斷。動態載入JS先前有討論過,也不是難事。

第3點比較麻煩,研究了一下,IE似乎無法支援<script>的onload事件,不過我有在網路上找到高人提出的解決方案,利用setInterval持續檢查特定物件是否存在以判定載入完成並觸發後續作業,這樣就可以達成依序逐一載入的目標。(其中interval變數巧妙地運用了Closure技巧,讓我大開眼界。好用的Javascript Closure以後再專文介紹)

綜合上述的研究,我寫成以下的範例:

(function() {
    function importJS(src, look_for, onload) {
        var s = document.createElement('script');
        s.setAttribute('type', 'text/javascript');
        s.setAttribute('src', src);
        if (onload) wait_for_script_load(look_for, onload);
        if (eval("typeof " + look_for) == 'undefined') {
            var head = document.getElementsByTagName('head')[0];
            if (head) head.appendChild(s);
            else document.body.appendChild(s);
        }
    }
    function wait_for_script_load(look_for, callback) {
        var interval = setInterval(function() {
            if (eval("typeof " + look_for) != 'undefined') {
                    clearInterval(interval);
                    callback();      
                }
            }, 50);
    }
    importJS("somePath/jquery-1.2.6.js", "jQuery", function() {
        importJS("somePath/jquery.blockUI.js", "jQuery.blockUI", function() {
            $.blockUI({ message: "<span>Done!</span>" });
        });
    });
})();

註: 最外層我包了一個(function() { ... })();,由於匿名函數的封裝,其中使用的importJS, wait_for_script_load等函數名稱只會這段空間中有效,不會與網頁其他地方的函數命名衝突,理由是不希望因為插入控件而干擾到網頁其他部分,我認為這也算控件品質的衡量指標之一。

六度C

今年不只景氣冷,氣溫也一路屢創新低,彷彿跟股市在比賽誰才是跌無止境深不可測的強者... (真是兩個幼稚的傢伙,把大家給害慘了)

1/10日冷氣團發威,清晨在陽台測到了10度以下的低溫,心想,台北市區降到10度以下並不常見。腦海中還殘存小時候看過屋瓦冷到結霜的畫面,但年紀漸長似乎天氣就沒再那麼冷過,十來度就稱得上是"寒流"(是因為溫室效應嗎?),相形之下,今年冬天真夠冷的。

老天爺顯然是嫌我見識太過淺薄,不過9度就大驚小怪,於是今天早上又露了一手給我看: 六度!!

大家注意保暖,保重身體!

【茶包射手專欄】IIS用IP可登入、用機器名稱不行

同事來求救! 說他們有兩台IIS,用IE輸入http: //192.168.1.1/webApp可以順利用網域帳號登入,但用http: //machineName/webApp卻會一直跳出帳號密碼登入對話框,輸入三次後則傳出驗證失敗錯誤。兩台IIS都有加入Domain,所以可以輸入機器名稱就連到主機;用IP時可以登入成功,表示IIS認得網域帳號,與DC間的連繫應該也是OK的。

由現象來看,問題出在IIS Authentication過程,於是我首先想到的是用Fiddler觀察認證過程。比對之下,發現二者的認證過程有差異,當輸入機器名稱時,IE一開始會送出NTLM認證,但從第三個Request起卻會變成Kerberos... (IP時則是從頭到尾使用NTLM)

或許有些人有印象,我之前處理過一次WSS一直跳出帳號密碼的狀況,當時也是Kerberos惹的禍。我對AD的研究不深,無力參透IIS與IE間何時會用NTLM,何時改走Kerberos的祕密,不過照方煎藥倒難不倒我。

在有問題的機器上執行cscript adsutil.vbs set w3svc/1/ROOT/NTAuthenticationProviders "NTLM",限定IIS只准用NTLM。再測一次,果然用機器名稱也可順利登入了。但問題真正的原因仍然是個謎,看看過往的高手達人們,有沒有人要出面露兩手為大家解惑。

連二拉二

這兩年起,每年元旦除了倒數,另外一件要事就是要關心自己有沒有被踢出MVP榜外。MVP每年有四梯選拔,我參選的是第三梯,照例所有的當選人會在1/1日結束前(美國時間的1/1日凌晨)接到微軟總部寄出的通知信。

今年有點小驚嚇,1/2日早上在幾個常用信箱裡翻箱倒櫃,卻都找不到通知信... 心中發寒,免費的MSDN沒了嗎?

後來靈機一動,總算在垃圾信匣發現信被誤判成廣告信的通知書。

感謝各位鄉親父老的支持愛護,微軟公司再次不棄嫌地"給老人家一個機會"(借用茂伯對馬拉桑的對白),MVP2009再次連莊成功。

週六收到了MVP Award Kit,不同於2007有行動碟與簡報雷射筆、2008有藍牙耳機+藍牙滑鼠,MVP2009的紀念禮品看起來像是一個十分精緻的壓克力水晶獎座,不過身為技客(Geek),已經很習慣在MVP紀念盒中找到可以載在頭上或拿在手上,有開關按鈕可以點點按按的科技小玩意(Gadget)。測試了一下,獎座頂在頭上很重很不舒服也不容易維持平衡,有隨時會摔下來的可能;換拿在手上,太沈了拿久手會酸,而且試著在表面上用手指拖拉甚至像iPhone用兩根手指拉開,文字及地球也不會旋轉或放大。好吧! 終於死心了,今年我得到的是一個充滿尊榮的紀念獎座,不用裝電池但也不提供其他科技功能。

今年還是會更努力當個稱職的MVP,請大家多多指教!

[更正】果然高檔貨送給不識貨的人猶如對牛彈琴、餵烏龜吃大麥... 另一位MVP Jesse提醒我,這獎座可是水晶打造的,不是什麼壓克力,市價要三千塊以上!! 在下的木眼拙手顯然辜負了MS的一片苦心... 幸好時尚品味不佳並不影響闡述技術時的正確度,本站的文章大家仍可安心服用。

祝大家新年快樂

2009年元旦至今消聲匿跡了好一陣子,由於已有"黑暗執行緒遭外星人擄走"的傳聞在坊間流傳,特浮出水面跟大家打個招呼以正視聽。

最近老天爺很給面子,逢假日都會賞幾個好天氣。元旦連假冷鋒來襲,但卻有兩天天氣好得出奇。兒子老嚷著要我帶著他去爬山,週六一早帶他去登了樟山寺。為了要爬山,這小子從叫起床上廁所吃早餐到穿衣穿鞋,一氣喝成,毫不脫泥帶水,跟平日上課判若兩人...

一路上連哄帶騙,總算完成了政大<->樟山寺完全攻略,中間只有一小段抱著走了約200公尺,勉強算是四歲小孩的首次自力登頂行程。

 

中午吃完午飯,兒子果然體力不支,吵著要午睡。見天氣大好,加上上午陪公子爬山的行程完全沒有舒展筋骨的效果,下午特地走了趟猴山岳,天公作美,視野極佳,雖然沒能看到海,但大半個台北市盡收眼底,夠本了。

不過,天氣真冷,騎機車要厚重保暖、登山得輕巧排汗,穿衣的拿捏甚為巧妙,結果看完大景就感冒了...

據說平日不常生病的人,一感冒就特別嚴重,彷彿要把一年份的症狀一次發作完。連假後的幾個上班日就在頭痛+發熱+畏寒+酸痛+喉癢+劇咳+失聲中度過,每天回家倒頭就睡,直到昨天下午實在受不了,請假看了醫生,今天再休一天以免帶著病毒到公司四處戕害同仁。因為如此,部落格也就跟著荒廢數日。

今天看來這感冒算是進入尾聲了,雖然起頭有點小小狀況,但願2009年會是順順利利的一年。

附上陽台花盆裡發現的四葉幸運草,祝2009年大家都順利平安,好運不斷。

更多文章 下一頁 »

搜尋

Go

<January 2009>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication