整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Html5之Bootstrap框架介紹

          ootstrap是Twitter推出的一個開源的用于前端開發的工具包。它由Twitter的設計師Mark Otto和Jacob Thornton合作開發,是一個CSS/HTML框架。Bootstrap提供了優雅的HTML和CSS規范,它即是由動態CSS語言Less寫成。Bootstrap一經推出后頗受歡迎,一直是GitHub上的熱門開源項目,包括NASA的MSNBC的Breaking News都使用了該項目。

          Bootstrap是基于HTML5和CSS3開發的,它在jQuery的基礎上進行了更為個性化和人性化的完善,形成一套自己獨有的網站風格,并兼容大部分jQuery插件。

          Bootstrap中包含了豐富的Web組件,根據這些組件,可以快速的搭建一個漂亮、功能完備的網站。其中包括以下組件:下拉菜單、按鈕組、按鈕下拉菜單、導航、導航條、面包屑、分頁、排版、縮略圖、警告對話框、進度條、媒體對象等。

          Bootstrap自帶了13個jQuery插件,這些插件為Bootstrap中的組件賦予了“生命”。其中包括:模式對話框、標簽頁、滾動條、彈出框等。

          Bootstrap Less是一個 CSS 預處理器,讓 CSS 具有動態性。另一方面,Bootstrap 是一個快速開發 Web App 和站點的工具包。這樣,您可以在 CSS 中使用 Bootstrap 的 Less 變量、mixins(混合

          )和 nesting(嵌套)。

          更多內容和資訊或者問題,更多干貨分享,盡在我的個人微信公眾號,微信名:非著名程序員,微信號:smart_android(←長按復制)。個人微信號:loonggg微博:澀郎

          源:《JavaScript設計模式與開發實踐》

          模式和重構之間有著一種與生俱來的關系。從某種角度來看,設計模式的目的就是為許多重構行為提供目標。

          1.提煉函數

          在JavaScript開發中,我們大部分時間都在與函數打交道,所以我們希望這些函數有著良好的命名,函數體內包含的邏輯清晰明了。如果一個函數過長,不得不加上若干注釋才能讓這個函數顯得易讀一些,那這些函數就很有必要進行重構。

          如果在函數中有一段代碼可以被獨立出來,那我們最好把這些代碼放進另外一個獨立的函數中。這是一種很常見的優化工作,這樣做的好處主要有以下幾點。

          • 避免出現超大函數。
          • 獨立出來的函數有助于代碼復用。
          • 獨立出來的函數更容易被覆寫。
          • 獨立出來的函數如果擁有一個良好的命名,它本身就起到了注釋的作用。

          比如在一個負責取得用戶信息的函數里面,我們還需要打印跟用戶信息有關的log,那么打印log的語句就可以被封裝在一個獨立的函數里:

          var getUserInfo = function(){
              ajax( 'http:// xxx.com/userInfo', function( data ){
                  console.log( 'userId: ' + data.userId );
                  console.log( 'userName: ' + data.userName );
                  console.log( 'nickName: ' + data.nickName );
              });
          };
          
          改成:
          
          var getUserInfo = function(){
              ajax( 'http:// xxx.com/userInfo', function( data ){
                  printDetails( data );
              });
          };
          
          var printDetails = function( data ){
              console.log( 'userId: ' + data.userId );
              console.log( 'userName: ' + data.userName );
              console.log( 'nickName: ' + data.nickName );
          };
          

          2.合并重復的條件片段

          如果一個函數體內有一些條件分支語句,而這些條件分支語句內部散布了一些重復的代碼,那么就有必要進行合并去重工作。假如我們有一個分頁函數paging,該函數接收一個參數currPage,currPage表示即將跳轉的頁碼。在跳轉之前,為防止currPage傳入過小或者過大的數字,我們要手動對它的值進行修正,詳見如下偽代碼:

          var paging = function( currPage ){
              if ( currPage <= 0 ){
                  currPage = 0;
                  jump( currPage );    // 跳轉
              }else if ( currPage >= totalPage ){
                  currPage = totalPage;
                  jump( currPage );    // 跳轉
              }else{
                  jump( currPage );    // 跳轉
              }
          };
          

          可以看到,負責跳轉的代碼jump( currPage )在每個條件分支內都出現了,所以完全可以把這句代碼獨立出來:

          var paging = function( currPage ){
              if ( currPage <= 0 ){
                  currPage = 0;
              }else if ( currPage >= totalPage ){
                  currPage = totalPage;
              }
              jump( currPage );    // 把jump函數獨立出來
          };
          

          3.把條件分支語句提煉成函數

          在程序設計中,復雜的條件分支語句是導致程序難以閱讀和理解的重要原因,而且容易導致一個龐大的函數。假設現在有一個需求是編寫一個計算商品價格的getPrice函數,商品的計算只有一個規則:如果當前正處于夏季,那么全部商品將以8折出售。代碼如下:

          var getPrice = function( price ){
              var date = new Date();
              if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){    // 夏天
                  return price * 0.8;
              }
              return price;
          };
          

          觀察這句代碼:

          if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){
              // ...
          }
          

          這句代碼要表達的意思很簡單,就是判斷當前是否正處于夏天(7~10月)。盡管這句代碼很短小,但代碼表達的意圖和代碼自身還存在一些距離,閱讀代碼的人必須要多花一些精力才能明白它傳達的意圖。其實可以把這句代碼提煉成一個單獨的函數,既能更準確地表達代碼的意思,函數名本身又能起到注釋的作用。代碼如下:

          var isSummer = function(){
              var date = new Date();
              return date.getMonth() >= 6 && date.getMonth() <= 9;
          };
          
          var getPrice = function( price ){
              if ( isSummer() ){    // 夏天
                  return price * 0.8;
              }
              return price;
          };
          

          4.合理使用循環

          在函數體內,如果有些代碼實際上負責的是一些重復性的工作,那么合理利用循環不僅可以完成同樣的功能,還可以使代碼量更少。下面有一段創建XHR對象的代碼,為了簡化示例,我們只考慮版本9以下的IE瀏覽器,代碼如下:

          var createXHR = function(){
              var xhr;
              try{
                  xhr = new ActiveXObject( 'MSXML2.XMLHttp.6.0' );
              }catch(e){
                  try{
                      xhr = new ActiveXObject( 'MSXML2.XMLHttp.3.0' );
                  }catch(e){
                      xhr = new ActiveXObject( 'MSXML2.XMLHttp' );
                  }
              }
              return xhr;
          };
          
          var xhr = createXHR();
          

          下面我們靈活地運用循環,可以得到跟上面代碼一樣的效果:

          var createXHR = function(){
          var versions= [ 'MSXML2.XMLHttp.6.0ddd', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp' ];
              for ( var i = 0, version; version = versions[ i++ ]; ){
                  try{
                      return new ActiveXObject( version );
                  }catch(e){
          
                  }
              }
          };
          
          var xhr = createXHR();
          

          5.提前讓函數退出代替嵌套條件分支

          許多程序員都有這樣一種觀念:“每個函數只能有一個入口和一個出口。”現代編程語言都會限制函數只有一個入口。但關于“函數只有一個出口”,往往會有一些不同的看法。

          下面這段偽代碼是遵守“函數只有一個出口的”的典型代碼:

          var del = function( obj ){
              var ret;
              if ( !obj.isReadOnly ){    // 不為只讀的才能被刪除
                  if ( obj.isFolder ){    // 如果是文件夾
                      ret = deleteFolder( obj );
                  }else if ( obj.isFile ){    // 如果是文件
                      ret = deleteFile( obj );
                  }
              }
              return ret;
          };
          

          嵌套的條件分支語句絕對是代碼維護者的噩夢,對于閱讀代碼的人來說,嵌套的if、else語句相比平鋪的if、else,在閱讀和理解上更加困難,有時候一個外層if分支的左括號和右括號之間相隔500米之遠。用《重構》里的話說,嵌套的條件分支往往是由一些深信“每個函數只能有一個出口的”程序員寫出的。但實際上,如果對函數的剩余部分不感興趣,那就應該立即退出。引導閱讀者去看一些沒有用的else片段,只會妨礙他們對程序的理解。

          于是我們可以挑選一些條件分支,在進入這些條件分支之后,就立即讓這個函數退出。要做到這一點,有一個常見的技巧,即在面對一個嵌套的if分支時,我們可以把外層if表達式進行反轉。重構后的del函數如下:

          var del = function( obj ){
              if ( obj.isReadOnly ){    // 反轉if表達式
                  return;
              }
              if ( obj.isFolder ){
                  return deleteFolder( obj );
              }
              if ( obj.isFile ){
                  return deleteFile( obj );
              }
          };
          

          6.傳遞對象參數代替過長的參數列表

          有時候一個函數有可能接收多個參數,而參數的數量越多,函數就越難理解和使用。使用該函數的人首先得搞明白全部參數的含義,在使用的時候,還要小心翼翼,以免少傳了某個參數或者把兩個參數搞反了位置。如果我們想在第3個參數和第4個參數之中增加一個新的參數,就會涉及許多代碼的修改,代碼如下:

          var setUserInfo = function( id, name, address, sex, mobile, qq ){
              console.log( 'id= ' + id );
              console.log( 'name= ' +name );
              console.log( 'address= ' + address );
              console.log( 'sex= ' + sex );
              console.log( 'mobile= ' + mobile );
              console.log( 'qq= ' + qq );
          };
          
          setUserInfo( 1314, 'sven', 'shenzhen', 'male', '137********', 377876679 )
          

          ;

          這時我們可以把參數都放入一個對象內,然后把該對象傳入setUserInfo 函數,setUserInfo函數需要的數據可以自行從該對象里獲取。現在不用再關心參數的數量和順序,只要保證參數對應的key值不變就可以了:

          var setUserInfo = function( obj ){
              console.log( 'id= ' + obj.id );
              console.log( 'name= ' + obj.name );
              console.log( 'address= ' + obj.address );
              console.log( 'sex= ' + obj.sex );
              console.log( 'mobile= ' + obj.mobile );
              console.log( 'qq= ' + obj.qq );
          };
          
          setUserInfo({
              id: 1314,
              name: 'sven',
              address: 'shenzhen',
              sex: 'male',
              mobile: '137********',
              qq: 377876679
          });
          

          7.盡量減少參數數量

          如果調用一個函數時需要傳入多個參數,那這個函數是讓人望而生畏的,我們必須搞清楚這些參數代表的含義,必須小心翼翼地把它們按照順序傳入該函數。而如果一個函數不需要傳入任何參數就可以使用,這種函數是深受人們喜愛的。在實際開發中,向函數傳遞參數不可避免,但我們應該盡量減少函數接收的參數數量。下面舉個非常簡單的示例。 有一個畫圖函數draw,它現在只能繪制正方形,接收了3個參數,分別是圖形的width、heigth以及square:

          var draw = function( width, height, square ){};
          

          但實際上正方形的面積是可以通過width和height計算出來的,于是我們可以把參數square從draw函數中去掉:

          var draw = function( width, height ){
              var square = width * height;
          };
          

          假設以后這個draw函數開始支持繪制圓形,我們需要把參數width和height換成半徑radius, 但圖形的面積square始終不應該由客戶傳入,而是應該在draw函數內部,由傳入的參數加上一定的規則計算得來。此時,我們可以使用策略模式,讓draw函數成為一個支持繪制多種圖形的函數。

          8.少用三目運算符

          有一些程序員喜歡大規模地使用三目運算符,來代替傳統的if、else。理由是三目運算符性能高,代碼量少。不過,這兩個理由其實都很難站得住腳。

          即使我們假設三目運算符的效率真的比if、else高,這點差距也是完全可以忽略不計的。在實際的開發中,即使把一段代碼循環一百萬次,使用三目運算符和使用if、else的時間開銷處在同一個級別里。

          同樣,相比損失的代碼可讀性和可維護性,三目運算符節省的代碼量也可以忽略不計。讓JS文件加載更快的辦法有很多種,如壓縮、緩存、使用CDN和分域名等。把注意力只放在使用三目運算符節省的字符數量上,無異于一個300斤重的人把超重的原因歸罪于頭皮屑。

          如果條件分支邏輯簡單且清晰,這無礙我們使用三目運算符:

          var global = typeof window !== "undefined" ? window : this;
          

          但如果條件分支邏輯非常復雜,如下段代碼所示,那我們最好的選擇還是按部就班地編寫if、else。if、else語句的好處很多,一是閱讀相對容易,二是修改的時候比修改三目運算符周圍的代碼更加方便:

          if ( !aup || !bup ) {
              return a === doc ? -1 :
                  b === doc ? 1 :
                  aup ? -1 :
                  bup ? 1 :
                  sortInput ?
                  ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
                  0;
          }
          

          9.合理使用鏈式調用

          經常使用jQuery的程序員相當習慣鏈式調用方法,在JavaScript中,可以很容易地實現方法的鏈式調用,即讓方法調用結束后返回對象自身,如下代碼所示:

          var User = function(){
              this.id = null;
              this.name = null;
          };
          
          User.prototype.setId = function( id ){
              this.id = id;
              return this;
          };
          
          User.prototype.setName = function( name ){
              this.name = name;
              return this;
          };
          
          console.log( new User().setId( 1314 ).setName( 'sven' ) );
          

          或者:

          var User = {
              id: null,
              name: null,
              setId: function( id ){
                  this.id = id;
                  return this;
              },
              setName: function( name ){
                  this.name = name;
                  return this;
              }
          };
          
          console.log( User.setId( 1314 ).setName( 'sven' ) );
          

          使用鏈式調用的方式并不會造成太多閱讀上的困難,也確實能省下一些字符和中間變量,但節省下來的字符數量同樣是微不足道的。鏈式調用帶來的壞處就是在調試的時候非常不方便,如果我們知道一條鏈中有錯誤出現,必須得先把這條鏈拆開才能加上一些調試log或者增加斷點,這樣才能定位錯誤出現的地方。

          如果該鏈條的結構相對穩定,后期不易發生修改,那么使用鏈式調用無可厚非。但如果該鏈條很容易發生變化,導致調試和維護困難,那么還是建議使用普通調用的形式:

          var user = new User();
          
          user.setId( 1314 );
          user.setName( 'sven' );
          

          10.分解大型類

          在HTML5版“街頭霸王”的第一版代碼中,負責創建游戲人物的Spirit 類非常龐大,不僅要負責創建人物精靈,還包括了人物的攻擊、防御等動作方法,代碼如下:

          var Spirit = function( name ){
              this.name = name;
          };
          
          Spirit.prototype.attack = function( type ){    // 攻擊
              if ( type === 'waveBoxing' ){
                  console.log( this.name + ': 使用波動拳' );
              }else if( type === 'whirlKick' ){
                  console.log( this.name + ': 使用旋風腿' );
              }
          };
          
          var spirit = new Spirit( 'RYU' );
          
          spirit.attack( 'waveBoxing' );      // 輸出:RYU: 使用波動拳
          spirit.attack( 'whirlKick' );    // 輸出:RYU: 使用旋風腿
          

          后來發現,Spirit.prototype.attack這個方法實現是太龐大了,實際上它完全有必要作為一個單獨的類存在。面向對象設計鼓勵將行為分布在合理數量的更小對象之中:

          var Attack = function( spirit ){
              this.spirit = spirit;
          };
          
          Attack.prototype.start = function( type ){
              return this.list[ type ].call( this );
          };
          
          Attack.prototype.list = {
              waveBoxing: function(){
                  console.log( this.spirit.name + ': 使用波動拳' );
              },
              whirlKick: function(){
                  console.log( this.spirit.name + ': 使用旋風腿' );
              }
          };
          

          現在的Spirit類變得精簡了很多,不再包括各種各樣的攻擊方法,而是把攻擊動作委托給Attack類的對象來執行,這段代碼也是策略模式的運用之一:

          var Spirit = function( name ){
              this.name = name;
              this.attackObj = new Attack( this );
          };
          
          Spirit.prototype.attack = function( type ){    // 攻擊
              this.attackObj.start( type );
          };
          
          var spirit = new Spirit( 'RYU' );
          
          spirit.attack( 'waveBoxing' );    // 輸出:RYU: 使用波動拳
          spirit.attack( 'whirlKick' );    // 輸出:RYU: 使用旋風
          

          11.用return退出多重循環

          假設在函數體內有一個兩重循環語句,我們需要在內層循環中判斷,當達到某個臨界條件時退出外層的循環。我們大多數時候會引入一個控制標記變量:

          var func = function(){
              var flag = false;
              for ( var i = 0; i < 10; i++ ){
                  for ( var j = 0; j < 10; j++ ){
                      if ( i * j >30 ){
                          flag = true;
                          break;
                      }
                  }
                  if ( flag === true ){
                      break;
                  }
              }
          };
          

          第二種做法是設置循環標記:

          var func = function(){
              outerloop:
              for ( var i = 0; i < 10; i++ ){
                  innerloop:
                  for ( var j = 0; j < 10; j++ ){
                      if ( i * j >30 ){
                          break outerloop;
                      }
                  }
              }
          };
          

          這兩種做法無疑都讓人頭暈目眩,更簡單的做法是在需要中止循環的時候直接退出整個方法:

          var func = function(){
              for ( var i = 0; i < 10; i++ ){
                  for ( var j = 0; j < 10; j++ ){
                      if ( i * j >30 ){
                          return;
                      }
                  }
              }
          };
          

          當然用return直接退出方法會帶來一個問題,如果在循環之后還有一些將被執行的代碼呢?如果我們提前退出了整個方法,這些代碼就得不到被執行的機會:

          var func = function(){
              for ( var i = 0; i < 10; i++ ){
                  for ( var j = 0; j < 10; j++ ){
                      if ( i * j >30 ){
                          return;
                      }
                  }
              }
              console.log( i );    // 這句代碼沒有機會被執行
          };
          

          為了解決這個問題,我們可以把循環后面的代碼放到return后面,如果代碼比較多,就應該把它們提煉成一個單獨的函數:

          var print = function( i ){
              console.log( i );
          };
          
          var func = function(){
              for ( var i = 0; i < 10; i++ ){
                  for ( var j = 0; j < 10; j++ ){
                      if ( i * j >30 ){
                          return print( i );
                      }
                  }
              }
          };
          
          func();


          - END -

          譯自: https://opensource.com/article/18/9/using-grails-jquery-and-datatables

          作者: Chris Hermansen

          譯者: jrg

          本文介紹如何構建一個基于 Grails 的數據瀏覽器來可視化復雜的表格數據。

          我是 Grails 的忠實粉絲。當然,我主要是熱衷于利用命令行工具來探索和分析數據的數據從業人員。數據從業人員經常需要查看數據,這也意味著他們通常擁有優秀的數據瀏覽器。利用 Grails、 jQuery ,以及 DataTables jQuery 插件 ,我們可以制作出非常友好的表格數據瀏覽器。

          DataTables 網站 提供了許多“食譜式”的教程文檔,展示了如何組合一些優秀的示例應用程序,這些程序包含了完成一些非常漂亮的東西所必要的 JavaScript、HTML,以及偶爾出現的 PHP 。但對于那些寧愿使用 Grails 作為后端的人來說,有必要進行一些說明示教。此外,樣本程序中使用的數據是一個虛構公司的員工的單個平面表格數據,因此處理這些復雜的表關系可以作為讀者的一個練習項目。

          本文中,我們將創建具有略微復雜的數據結構和 DataTables 瀏覽器的 Grails 應用程序。我們將介紹 Grails 標準,它是 Groovy 式的 Java Hibernate 標準。我已將代碼托管在 GitHub 上方便大家訪問,因此本文主要是對代碼細節的解讀。

          首先,你需要配置 Java、Groovy、Grails 的使用環境。對于 Grails,我傾向于使用終端窗口和 Vim ,本文也使用它們。為獲得現代的 Java 環境,建議下載并安裝 Linux 發行版提供的 Open Java Development Kit (OpenJDK)(應該是 Java 8、9、10 或 11 之一,撰寫本文時,我正在使用 Java 8)。從我的角度來看,獲取最新的 Groovy 和 Grails 的最佳方法是使用 SDKMAN! 。

          從未嘗試過 Grails 的讀者可能需要做一些背景資料閱讀。作為初學者,推薦文章 創建你的第一個 Grails 應用程序 。

          獲取員工信息瀏覽器應用程序

          正如上文所提,我將本文中員工信息瀏覽器的源代碼托管在 GitHub 上。進一步講,應用程序 embrow 是在 Linux 終端中用如下命令構建的:

          cd Projects

          grails create-app com.nuevaconsulting.embrow

          域類和單元測試創建如下:

          grails create-domain-class com.nuevaconsulting.embrow.Position

          grails create-domain-class com.nuevaconsulting.embrow.Office

          grails create-domain-class com.nuevaconsulting.embrow.Employeecd embrowgrails createdomaincom.grails createdomaincom.grails createdomaincom.

          這種方式構建的域類沒有屬性,因此必須按如下方式編輯它們:

          Position 域類:

          package com.nuevaconsulting.embrow

          class Position {

          String name

          int starting

          static constraints = {

          name nullable: false, blank: false

          starting nullable: false

          }

          }com.Stringint startingstatic constraintsnullableblankstarting nullable

          Office 域類:

          package com.nuevaconsulting.embrow

          class Office {

          String name

          String address

          String city

          String country

          static constraints = {

          name nullable: false, blank: false

          address nullable: false, blank: false

          city nullable: false, blank: false

          country nullable: false, blank: false

          }

          }

          Enployee 域類:

          package com.nuevaconsulting.embrow

          class Employee {

          String surname

          String givenNames

          Position position

          Office office

          int extension

          Date hired

          int salary

          static constraints = {

          surname nullable: false, blank: false

          givenNames nullable: false, blank: false

          : false

          office nullable: false

          extension nullable: false

          hired nullable: false

          salary nullable: false

          }

          }

          請注意,雖然 Position 和 Office 域類使用了預定義的 Groovy 類型 String 以及 int,但 Employee 域類定義了 Position 和 Office 字段(以及預定義的 Date)。這會導致創建數據庫表,其中存儲的 Employee 實例中包含了指向存儲 Position 和 Office 實例表的引用或者外鍵。

          現在你可以生成控制器,視圖,以及其他各種測試組件:

          -all com.nuevaconsulting.embrow.Position

          grails generate-all com.nuevaconsulting.embrow.Office

          grails generate-all com.nuevaconsulting.embrow.Employeegrails generateall com.grails generateall com.grails generateall com.

          此時,你已經準備好了一個基本的增刪改查(CRUD)應用程序。我在 grails-app/init/com/nuevaconsulting/BootStrap.groovy 中包含了一些基礎數據來填充表格。

          如果你用如下命令來啟動應用程序:

          grails run-app

          在瀏覽器輸入 http://localhost:8080/,你將會看到如下界面:



          Embrow 應用程序主界面。

          單擊 “OfficeController” 鏈接,會跳轉到如下界面:



          Office 列表

          注意,此表由 OfficeController 的 index 方式生成,并由視圖 office/index.gsp 顯示。

          同樣,單擊 “EmployeeController” 鏈接 跳轉到如下界面:



          employee 控制器

          好吧,這很丑陋: Position 和 Office 鏈接是什么?

          上面的命令 generate-all 生成的視圖創建了一個叫 index.gsp 的文件,它使用 Grails <f:table/> 標簽,該標簽默認會顯示類名(com.nuevaconsulting.embrow.Position)和持久化示例標識符(30)。這個操作可以自定義用來產生更好看的東西,并且自動生成鏈接,自動生成分頁以及自動生成可排序列的一些非常簡潔直觀的東西。

          但該員工信息瀏覽器功能也是有限的。例如,如果想查找 “position” 信息中包含 “dev” 的員工該怎么辦?如果要組合排序,以姓氏為主排序關鍵字,“office” 為輔助排序關鍵字,該怎么辦?或者,你需要將已排序的數據導出到電子表格或 PDF 文檔以便通過電子郵件發送給無法訪問瀏覽器的人,該怎么辦?

          jQuery DataTables 插件提供了這些所需的功能。允許你創建一個完成的表格數據瀏覽器。

          創建員工信息瀏覽器視圖和控制器的方法

          要基于 jQuery DataTables 創建員工信息瀏覽器,你必須先完成以下兩個任務:

          1. 創建 Grails 視圖,其中包含啟用 DataTable 所需的 HTML 和 JavaScript
          2. 給 Grails 控制器增加一個方法來控制新視圖。

          員工信息瀏覽器視圖

          在目錄 embrow/grails-app/views/employee 中,首先復制 index.gsp 文件,重命名為 browser.gsp:

          cd Projects

          cd embrow/grails-app/views/employee

          cp gsp browser.gsp

          此刻,你自定義新的 browser.gsp 文件來添加相關的 jQuery DataTables 代碼。

          通常,在可能的時候,我喜歡從內容提供商處獲得 JavaScript 和 CSS;在下面這行后面:

          <title><g:message code="default.list.label" args="[entityName]" /></title>

          插入如下代碼:

          <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>

          <link rel="stylesheet" type="text/css" >

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script>

          <link rel="stylesheet" type="text/css" >

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/scroller/1.4.4/js/dataTables.scroller.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.flash.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/pdfmake.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/vfs_fonts.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.html5.min.js"></script>

          <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.print.min.js "></script>

          然后刪除 index.gsp 中提供數據分頁的代碼:

          <div id="list-employee" class="content scaffold-list" role="main">

          <h1><g:message code="default.list.label" args="[entityName]" /></h1>

          <g:if test="${flash.message}">

          <div class="message" role="status">${flash.message}</div>

          </g:if>

          <f:table collection="${employeeList}" />

          <div class="pagination">

          <g:paginate total="${employeeCount ?: 0}" />

          </div>

          </div>

          并插入實現 jQuery DataTables 的代碼。

          要插入的第一部分是 HTML,它將創建瀏覽器的基本表格結構。DataTables 與后端通信的應用程序來說,它們只提供表格頁眉和頁腳;DataTables JavaScript 則負責表中內容。

          <div id="employee-browser" class="content" role="main">

          <h1>Employee Browser</h1>

          <table id="employee_dt" class="display compact" style="width:99%;">

          <thead>

          <tr>

          <th>Surname</th>

          <th>Given name(s)</th>

          <th>Position</th>

          <th>Office</th>

          <th>Extension</th>

          <th>Hired</th>

          <th>Salary</th>

          </tr>

          </thead>

          <tfoot>

          <tr>

          <th>Surname</th>

          <th>Given name(s)</th>

          <th>Position</th>

          <th>Office</th>

          <th>Extension</th>

          <th>Hired</th>

          <th>Salary</th>

          </tr>

          </tfoot>

          </table>

          </div>

          接下來,插入一個 JavaScript 塊,它主要提供三個功能:它設置頁腳中顯示的文本框的大小,以進行列過濾,建立 DataTables 表模型,并創建一個處理程序來進行列過濾。

          <g:javascript>

          $('#employee_dt tfoot th').each( function() {javascript

          下面的代碼處理表格列底部的過濾器框的大小:

          var title = $(this).text();

          if (title == 'Extension' || title == 'Hired')

          $(this).html('<input type="text" size="5" placeholder="' + title + '?" />');

          else

          $(this).html('<input type="text" size="15" placeholder="' + title + '?" />');

          });titletitletitletitletitle

          接下來,定義表模型。這是提供所有表選項的地方,包括界面的滾動,而不是分頁,根據 DOM 字符串提供的裝飾,將數據導出為 CSV 和其他格式的能力,以及建立與服務器的 AJAX 連接。 請注意,使用 Groovy GString 調用 Grails createLink() 的方法創建 URL,在 EmployeeController 中指向 browserLister 操作。同樣有趣的是表格列的定義。此信息將發送到后端,后端查詢數據庫并返回相應的記錄。

          var table = $('#employee_dt').DataTable( {

          "scrollY": 500,

          "deferRender": true,

          "scroller": true,

          "dom": "Brtip",

          "buttons": [ 'copy', 'csv', 'excel', 'pdf', 'print' ],

          "processing": true,

          "serverSide": true,

          "ajax": {

          "url": "${createLink(controller: 'employee', action: 'browserLister')}",

          "type": "POST",

          },

          "columns": [

          { "data": "surname" },

          { "data": "givenNames" },

          { "data": "position" },

          { "data": "office" },

          { "data": "extension" },

          { "data": "hired" },

          { "data": "salary" }

          ]

          });

          最后,監視過濾器列以進行更改,并使用它們來應用過濾器。

          table.columns().every(function() {

          var that = this;

          $('input', this.footer()).on('keyup change', function(e) {

          if (that.search() != this.value && 8 < e.keyCode && e.keyCode < 32)

          that.search(this.value).draw();

          });

          這就是 JavaScript,這樣就完成了對視圖代碼的更改。

          });

          </g:javascript>

          以下是此視圖創建的UI的屏幕截圖:



          這是另一個屏幕截圖,顯示了過濾和多列排序(尋找 “position” 包括字符 “dev” 的員工,先按 “office” 排序,然后按姓氏排序):



          這是另一個屏幕截圖,顯示單擊 CSV 按鈕時會發生什么:



          最后,這是一個截圖,顯示在 LibreOffice 中打開的 CSV 數據:



          好的,視圖部分看起來非常簡單;因此,控制器必須做所有繁重的工作,對吧? 讓我們來看看……

          控制器 browserLister 操作

          回想一下,我們看到過這個字符串:

          "${createLink(controller: 'employee', action: 'browserLister')}"

          對于從 DataTables 模型中調用 AJAX 的 URL,是在 Grails 服務器上動態創建 HTML 鏈接,其 Grails 標記背后通過調用 createLink() 的方法實現的。這會最終產生一個指向 EmployeeController 的鏈接,位于:

          embrow/grails-app/controllers/com/nuevaconsulting/embrow/EmployeeController.groovy

          特別是控制器方法 browserLister()。我在代碼中留了一些 print 語句,以便在運行時能夠在終端看到中間結果。

          def browserLister() {

          // Applies filters and sorting to return a list of desired employees

          首先,打印出傳遞給 browserLister() 的參數。我通常使用此代碼開始構建控制器方法,以便我完全清楚我的控制器正在接收什么。

          println "employee browserLister params $params"

          println()

          接下來,處理這些參數以使它們更加有用。首先,jQuery DataTables 參數,一個名為 jqdtParams 的 Groovy 映射:

          def jqdtParams = [:]

          params.each { key, value ->

          def keyFields = key.replace(']','').split(/\[/)

          def table = jqdtParams

          for (int f = 0; f < keyFields.size() - 1; f++) {

          def keyField = keyFields[f]

          if (!table.containsKey(keyField))

          table[keyField] = [:]

          table = table[keyField]

          }

          table[keyFields[-1]] = value

          }

          println "employee dataTableParams $jqdtParams"

          println()

          接下來,列數據,一個名為 columnMap 的 Groovy 映射:

          def columnMap = jqdtParams.columns.collectEntries { k, v ->

          def whereTerm = null

          switch (v.data) {

          case 'extension':

          case 'hired':

          case 'salary':

          if (v.search.value ==~ /\d+(,\d+)*/)

          whereTerm = v.search.value.split(',').collect { it as Integer }

          break

          default:

          if (v.search.value ==~ /[A-Za-z0-9 ]+/)

          whereTerm = "%${v.search.value}%" as String

          break

          }

          [(v.data): [where: whereTerm]]

          }

          println "employee columnMap $columnMap"

          println()

          接下來,從 columnMap 中檢索的所有列表,以及在視圖中應如何排序這些列表,Groovy 列表分別稱為 allColumnList 和 orderList :

          def allColumnList = columnMap.keySet() as List

          println "employee allColumnList $allColumnList"

          def orderList = jqdtParams.order.collect { k, v -> [allColumnList[v.column as Integer], v.dir] }

          println "employee orderList $orderList"

          我們將使用 Grails 的 Hibernate 標準實現來實際選擇要顯示的元素以及它們的排序和分頁。標準要求過濾器關閉;在大多數示例中,這是作為標準實例本身的創建的一部分給出的,但是在這里我們預先定義過濾器閉包。請注意,在這種情況下,“date hired” 過濾器的相對復雜的解釋被視為一年并應用于建立日期范圍,并使用 createAlias 以允許我們進入相關類別 Position 和 Office:

          def filterer = {

          createAlias 'position', 'p'

          createAlias 'office', 'o'

          if (columnMap.surname.where) ilike 'surname', columnMap.surname.where

          if (columnMap.givenNames.where) ilike 'givenNames', columnMap.givenNames.where

          if (columnMap.position.where) ilike 'p.name', columnMap.position.where

          if (columnMap.office.where) ilike 'o.name', columnMap.office.where

          if (columnMap.extension.where) inList 'extension', columnMap.extension.where

          if (columnMap.salary.where) inList 'salary', columnMap.salary.where

          if (columnMap.hired.where) {

          if (columnMap.hired.where.size() > 1) {

          or {

          columnMap.hired.where.each {

          between 'hired', Date.parse('yyyy/MM/dd',"${it}/01/01" as String),

          Date.parse('yyyy/MM/dd',"${it}/12/31" as String)

          }

          }

          } else {

          between 'hired', Date.parse('yyyy/MM/dd',"${columnMap.hired.where[0]}/01/01" as String),

          Date.parse('yyyy/MM/dd',"${columnMap.hired.where[0]}/12/31" as String)

          }

          }

          }

          是時候應用上述內容了。第一步是獲取分頁代碼所需的所有 Employee 實例的總數:

          def recordsTotal = Employee.count()

          println "employee recordsTotal $recordsTotal"

          接下來,將過濾器應用于 Employee 實例以獲取過濾結果的計數,該結果將始終小于或等于總數(同樣,這是針對分頁代碼):

          def c = Employee.createCriteria()

          def recordsFiltered = c.count {

          filterer.delegate = delegate

          filterer()

          }

          println "employee recordsFiltered $recordsFiltered"

          獲得這兩個計數后,你還可以使用分頁和排序信息獲取實際過濾的實例。

          def orderer = Employee.withCriteria {

          filterer.delegate = delegate

          filterer()

          orderList.each { oi ->

          switch (oi[0]) {

          case 'surname': order 'surname', oi[1]; break

          case 'givenNames': order 'givenNames', oi[1]; break

          case 'position': order 'p.name', oi[1]; break

          case 'office': order 'o.name', oi[1]; break

          case 'extension': order 'extension', oi[1]; break

          case 'hired': order 'hired', oi[1]; break

          case 'salary': order 'salary', oi[1]; break

          }

          }

          maxResults (jqdtParams.length as Integer)

          firstResult (jqdtParams.start as Integer)

          }

          要完全清楚,JTable 中的分頁代碼管理三個計數:數據集中的記錄總數,應用過濾器后得到的數字,以及要在頁面上顯示的數字(顯示是滾動還是分頁)。 排序應用于所有過濾的記錄,并且分頁應用于那些過濾的記錄的塊以用于顯示目的。

          接下來,處理命令返回的結果,在每行中創建指向 Employee、Position 和 Office 實例的鏈接,以便用戶可以單擊這些鏈接以獲取相關實例的所有詳細信息:

          def dollarFormatter = new DecimalFormat('$##,###.##')

          def employees = orderer.collect { employee ->

          ['surname': "<a href='${createLink(controller: 'employee', action: 'show', id: employee.id)}'>${employee.surname}</a>",

          'givenNames': employee.givenNames,

          'position': "<a href='${createLink(controller: 'position', action: 'show', id: employee.position?.id)}'>${employee.position?.name}</a>",

          'office': "<a href='${createLink(controller: 'office', action: 'show', id: employee.office?.id)}'>${employee.office?.name}</a>",

          'extension': employee.extension,

          'hired': employee.hired.format('yyyy/MM/dd'),

          'salary': dollarFormatter.format(employee.salary)]

          }

          最后,創建要返回的結果并將其作為 JSON 返回,這是 jQuery DataTables 所需要的。

          def result = [draw: jqdtParams.draw, recordsTotal: recordsTotal, recordsFiltered: recordsFiltered, data: employees]

          render(result as JSON)

          }

          大功告成。

          如果你熟悉 Grails,這可能看起來比你原先想象的要多,但這里沒有火箭式的一步到位方法,只是很多分散的操作步驟。但是,如果你沒有太多接觸 Grails(或 Groovy),那么需要了解很多新東西 - 閉包,代理和構建器等等。

          在那種情況下,從哪里開始? 最好的地方是了解 Groovy 本身,尤其是 Groovy closures 和 Groovy delegates and builders 。然后再去閱讀上面關于 Grails 和 Hibernate 條件查詢的建議閱讀文章。

          結語

          jQuery DataTables 為 Grails 制作了很棒的表格數據瀏覽器。對視圖進行編碼并不是太棘手,但 DataTables 文檔中提供的 PHP 示例提供的功能僅到此位置。特別是,它們不是用 Grails 程序員編寫的,也不包含探索使用引用其他類(實質上是查找表)的元素的更精細的細節。

          我使用這種方法制作了幾個數據瀏覽器,允許用戶選擇要查看和累積記錄計數的列,或者只是瀏覽數據。即使在相對適度的 VPS 上的百萬行表中,性能也很好。

          一個警告:我偶然發現了 Grails 中暴露的各種 Hibernate 標準機制的一些問題(請參閱我的其他 GitHub 代碼庫),因此需要謹慎和實驗。如果所有其他方法都失敗了,另一種方法是動態構建 SQL 字符串并執行它們。在撰寫本文時,我更喜歡使用 Grails 標準,除非我遇到雜亂的子查詢,但這可能只反映了我在 Hibernate 中對子查詢的相對缺乏經驗。

          我希望 Grails 程序員發現本文的有趣性。請隨時在下面留下評論或建議。


          via: https://opensource.com/article/18/9/using-grails-jquery-and-datatables

          作者: Chris Hermansen 選題: lujun9972 譯者: jrg 校對: wxy

          本文由 LCTT 原創編譯, Linux中國 榮譽推出

          點擊“了解更多”可訪問文內鏈接


          主站蜘蛛池模板: 亚洲国产一区二区a毛片| 日本一区二区三区在线视频观看免费 | 亚洲乱码av中文一区二区| 亚洲国产av一区二区三区| 无码日韩人妻AV一区免费l| 国产午夜精品一区二区三区极品| 久久青草精品一区二区三区| 日韩精品一区二区三区四区| 中文字幕一区二区日产乱码| 一区二区三区免费视频观看| 免费高清在线影片一区| 韩国精品一区视频在线播放| 在线电影一区二区三区| 久久久国产精品无码一区二区三区 | 韩国资源视频一区二区三区| 国产一区二区三区国产精品| 精品一区二区三区中文| 国产一区二区三区福利| 日日摸夜夜添一区| 日韩一区二区三区电影在线观看| 成人一区专区在线观看| 久久精品一区二区影院| 日韩人妻精品一区二区三区视频| 色窝窝无码一区二区三区色欲| 亚洲国产成人一区二区精品区| 韩国一区二区视频| 亚洲一区二区三区免费| 78成人精品电影在线播放日韩精品电影一区亚洲 | 日韩免费一区二区三区在线| 国产一区二区三区在线看片| 精品乱码一区二区三区四区| AV无码精品一区二区三区宅噜噜| 亚洲AV一区二区三区四区 | 色窝窝免费一区二区三区| 国产在线一区二区| 99久久精品国产免看国产一区| 在线免费观看一区二区三区| 国产香蕉一区二区精品视频| 亚洲色精品aⅴ一区区三区| 亚洲AV成人精品日韩一区18p | 国产乱码精品一区二区三区香蕉 |