ساخت اينتر‌فيس‌های كاربر Client-side در HTML


(قسمت اول)

 

استفاده مفيد از JEditorPane

 

نويسنده: Allen Holub
مترجم: شهناز پيروزفر

 

چكيده

اين مقاله به بحث درباره Swings Jeditorpane مي‌پردازد. Jeditorpane، شخصي سازي نمايشگر كلي كاربر Client-side را در HTML، امكان‌پذير مي‌نمايد. Jeditor pane به شما امكان مي‌دهد تا مولفه متن HTML را در جعبه محاوره بدون مشخص‌سازي محتويات كلي جعبه محاوره در HTML، قرار دهيد. در عمل اين كلاس به سهولت ساخت واسط‌هاي پيچيده كاربر كمك مي‌كند.

راههاي متعددي براي ساخت واسط‌هاي كاربر بدون آشكارسازي جزئيات پياده‌سازي وجود دارد و بلافاصله الگوهاي Gang of Four Builder، Visitor به فكر خطور مي‌كنند. به طور حتم متد ساده drawyourself() فقط با آبجكت‌هاي ساده كار مي‌كند و داشتن 50 متد Yourself InThis Format()، draw yourself In That Format() براي كد غير قابل مديريت، مناسب نيست.

با توجه به اينكه احكام واسط كاربر به خوبي درك شدند. قصد دارم چند پياده‌سازي شيوه‌هاي ساخت واسط كاربر شي‌گرا را ارائه دهم. اين مقاله به بررسي يكي از سيستم‌هاي واسط كاربر مي‌پردازد:

كلاس زيرساختي كه در ساخت واسط‌هاي كاربر Client-side به شيوه شي‌گرا استفاده نمودم. البته اين كلاس به تنهايي راهكاري براي مساله واسط كاربر محسوب نمي‌شود، اما بلوك ساختماني سودمندي است.

از آنجائيكه نمونه كدها بزرگ هستند، آنها را به دو گروه تقسيم نمودم كه در اين قسمت كد برنامه و مستندات و در قسمت بعد كد اصلي را ارائه مي‌دهم.

دو سري مقاله ساخت واسط‌هاي كاربر Client-side در HTML را در دو بخش زير مطالعه نماييد:

قسمت اول: استفاده مفيد از Jeditorpane

قسمت دوم: منابع HTML pane

 

بكارگيري HTML در Client-side

HTML عجيب است. HTML به شما امكان مي‌دهد تا واسط‌هاي كاربر پيچيده را با حداقل آشفتگي مستقر نماييد و ساختار واسط كاربر و استقرار را از منطق كسب‌و‌كار جدا مي‌سازد، نوشتن و نگهداري آن نيز آسان است. اما كاربرد طرح Abstract window Toolkit (AWT)/ Swing دشوار است. شما بايد كد را براي اعمال تغييرات در نمايشگر اصلاح نماييد (يا دوباره كامپايل كنيد) و از طرفي كد طرح واضح نيست و تعداد صفحات را افزايش مي دهد. آيا بهتر نبود كه مي‌توانستيد واسط كلي كاربر Client-side در HTML مشخص نماييد؟

(من مي‌دانم كه بعضي از شما در پاسخ به پرسش فوق مي‌گوييد: خير بسياري از افراد معتقدند كه HTML و تجربه خوب كاربر مفاهيم انحصاري هستند، زيرا HTML، واسط كاربر را در وضعيت كادر محاوره پشت سرهم نامحدود قرار مي دهد. از طرفي، بسياري از برنامه‌ها مي‌توانند به طور اثربخش بر HTML حداقل در بخشي از واسط كاربر، نفوذ كنند.

در ابتدا به نظر مي رسد كه كلاس Swings Jeditor pane پاسخ مساله طرح HTML باشد. اين كلاس ورودي HTML را درك مي‌كند. براي مثال كد زير فريمي را مي‌گشايد كه بعضي از متون ساده HTML را نمايش مي‌دهد:

 

JFrame      main_frame  = new JFrame();
JEditorPane pane        = new JEditorPane();

pane.setContentType ( "text/html" );
pane.setEditable    ( false );
pane.setText
(
    "<html>" +
    "<head>" +
    "</head>" +
    "<body>" +
        "<center><b>Hello</b> <i>World</i></center>" +
    "</body>" +
    "</html>"
);
main_frame.setContentPane(pane);
main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main_frame.pack();
main_frame.show();

 

همانطور كه گفتيم Jeditorpane براي مديريت HTML پيچيده مناسب نيست و كار خود را در زمينه جداول تودرتو و (Cascading Style Sheets) CSS به خوبي انجام نمي دهد (اين مشكلات در جاواي آتي يعني نسخه 1.5 رفع خواهد شد). سرانجام Jeditorpane  در استقرار دگمه‌هاي راديويي (radio buttons) نيز خوب عمل نمي‌كند. اين دگمه‌ها پس زمينه خاكستري را نمايش مي‌دهند و اگر رنگ پس زمينه را تغيير دهيد، به خوبي كار نمي‌كنند. اين اشكالات بسيار آزاردهنده هستند اما مشكل تاثيرگذار Jeditorpane اين است كه به صورت كنترل متن عمل مي‌كند نه امكان layout. شما مي‌توانيد HTML <form…> را براي مثال در ورودي مشخص كنيد، اما فرم با فشردن دگمه Submit به سرور وب ارسال مي‌شود. براي بكارگيري مفيد واسط كاربر Client-side، مايليد كه داده‌ها به برنامه‌اي كه فرم را نمايش مي‌دهد باز گردند، نه سرور راه دور. همچنين به شيوه مناسبي جهت افزودن تگ‌هاي سفارشي براي ورودي غير استاندارد يا تعيين مكاني براي نگهداري Swing Jcomponents استانداردي كه در فرم استفاده مي‌كنيد، نيازمنديد. (Jeditorpane به شما امكان مي‌دهد تا اين كار را انجام دهيد)در نهايت بايد بتوانيد مواردي نظير دگمه Cancel را كه در HTML وجود ندارد، مديريت كنيد.

خوشبختانه اين كارها با كمك امكانات سفارشي‌سازي كه در Jeditorpane تعبيه شده است، انجام مي‌گيرد و رفع اين مشكلات مخاطراتي نيز به همراه دارد. براي مثال، شما مي‌توانيد مشكل دگمه Cancel را با پياده‌سازي مفسر جاوا اسكريپت و پشتيباني از صفت onclick رفع كنيد، اما اين عمل به كار زيادي نياز دارد. همينطور پشتيباني صحيح از تگ سفارشي (در جايي كه مي‌توانيد هر چه بين تگ Start، end قرار دارد، پردازش كنيد) با parser موجود، بسيار دشوار است و شما مي‌توانيد Jeditorpane parser را با parser بهتري جايگزين نماييد، اما اين عمل نيز به كار زيادي نياز دارد. من راه كارهاي ساده‌تري را براي اين منظور انتخاب نمودم بدين ترتيب كه اين حالت را در عملكرد كلاس لحاظ نمودم و از آن براي ساخت واسط كاربر برنامه استفاده كردم، اما اين راهكار عالي نبود. مشكل در اين بود كه بايد راهي براي مشخص‌سازي واسط كاربر در HTML مي‌يافتم. من مشكل را رفع نكردم بلكه راهي براي نمايش همه HTML هاي احتمالي در برنامه Client-side فراهم كردم. كلاس HTMLPane ارائه شده در اين مقاله مشكل مشخص‌سازي واسط كاربر را در HTML، به خوبي رفع مي‌كند.

 

بكارگيري HTMLPane

HTMLPane، تنها كلاس Client-side ورودي HTML، يك Jeditorpane است كه مشكلات مذكور را رفع مي‌كند. ليست 1 نحوه بكارگيري HTMLPane را نشان مي دهد. من يك Jdialog ساده به نام HTML Dialog را ساختم كه به كمك آن مي‌توانيد طرح كادر را به صورت HTML مشخص كنيد. HTML Dialog مثالي واضح از الگوي طراحي Facade است كه عملكرد مورد نياز براي قرار دادن HTMLpane در كادر محاوره و نمايش آن را انجام مي‌دهد.

كلاس HTML Dialog Test (ليست 1، خط 134) مثال ساده‌اي از نحوه بكارگيري HTML Dialog است. اين كلاس تقريبا يك main frame خالي (مالك) را مي‌سازد. با كمك كد زير، main() يك آبجكت HTMLDialog مي‌سازد كه محتويات آن در فايل Com/holub/ui/HTML/test/okay.html (ليست 2) مشخص شده‌اند. رشته Test Html Dialog نيز در نوار عنوان ظاهر مي شود در نتيجه main() سبب باز شدن كادر محاوره با فراخواني d0popup() مي‌شود و تا هنگاميكه كاربر محاوره را متوقف ننموده، بازگردانده نخواهد شد.

 

// Display the okay.html file in a dialog box that has
// the title "Test HtmlDialog".
//
HtmlDialog dialog = new HtmlDialog(
                        owning_frame,
                        "com/holub/ui/HTML/test/okay.html",
                        "Test HtmlDialog");

// Pop up the dialog and wait for the user to dismiss it.
//
dialog.popup();

// Print the "form" data that the user typed.
//
System.out.println
(   "hidden="       + dialog.data().getProperty("hidden")
+   "user-input"    + dialog.data().getProperty("user-input")
);

 

داده‌هاي فرم (متني كه كاربر در عنصر <:nput…name= "X"…> تايپ مي‌كند)، از طريق متد data() در Html Dialog در دسترس قرار مي‌گيرند. اين متد آبجكت Java.uyil properties را باز مي‌گرداند. آبجكت مذكور زوج كليد/ مقدار نمايانگر داده هاي فرم را (formdata) جدا نگهداري مي‌كند. فراخواني درdialog.data() get property (“hidden”)  رشتهhidden-field data را باز مي‌گرداند.

فراخواني dialog.data().getproperty (user-input)، هر آنچه كه كاربر در فيلد ورودي تايپ نموده، باز مي‌گرداند.

اكثر كار مورد نظر در HTMLpane محصور شده (encapsulated) درHtml Dialog Constructor (ليست1، خط 46) روي مي دهد. ابتدا Constructor، Action Listener را تنظيم مي‌كند، Action Listener مديريت دگمه Submit را بر روي فرم بر عهده دارد. اين Observer، كادر محاوره فعلي را متوقف نموده و هرگونه داده فرم را از HTMLpane در متغير نمونه داده كپي مي‌كند. سپس Constructor فايل ورودي را از CLASS PATH دريافت و HTML را با كمك Set Text() در HTMLpane بارگذاري مي‌كند (در اينجا متد SetPage (URL) نيز وجود دارد، اما در صورت استفاده از آن به يك URL براي مسير فايل مورد نظرتان نياز داريد. من فايل HTML را CLASSPATH ) ناميدم.

پردازش Cancel در (popup() خط 121) انجام مي‌گيرد و فرض بر اين است كه دگمه Cancel در صورتي فشرده مي‌شود كه كليد Cancel در داده‌هاي فرم ارسالي وجود داشته باشد.

Listing 1. HtmlDialog.java

   1  package com.holub.ui.HTML;
   2  
   3  import javax.swing.*;
   4  import java.awt.*;
   5  import java.awt.event.*;
   6  import java.net.URL;
   7  import java.util.Properties;
   8  import java.io.IOException;
   9  
  10  
/**
  11   *  A model dialog that holds an {@link HTMLPane}. The dialog shuts
  12   *  down when the user hits a button created by either an
  13   *  <input type=submit> or (HTMLPane-specific)
  14   *  <inputAction> tag.
  15   *  Display the dialog by calling <code>show()</code>.
  16   *  For example:
  17   *  <PRE>
  18   *  HtmlDialog d = new HtmlDialog( owner,
  19   *                                "com/holub/ui/HTML/test/okay.html",
  20   *                                "Test HtmlDialog" );
  21   *  if( d.popup() )  // Dialog not cancelled.
  22   *      d.data().list( System.out );
  23   * </PRE>
  24   * The <a href="HTMLPane.html#customTags">default custom tags</a>
  25   * are all preinstalled in the underlying
  26   * {@link HTMLPane}.
  27   */

  28  
  29  public class HtmlDialog extends JDialog
  30  {
  31      private Properties data = null;
  32  
  33      private final HTMLPane pane = new HTMLPane(true);
  34  
  35      
/** Create and initialize a modal HTMLPane.
  36       *  @param owner        The Frame that "owns" this dialog (the parent).
  37       *  @param htmlPath The CLASSPATH-relative path to the .html file
  38       *                      that holds the contents. For example, if CLASSPATH
  39       *                      contains the directory /usr/src, then get the
  40       *                      file in /usr/src/html/test.html by specifying
  41       *                      <code>"html/test.html"</code> (no leading slash).
  42       *  @param title        Dialog-box title-bar contents.
  43       *  @throws IOException if it can't open the file on the htmlPath;
  44       */

  45  
  46      public HtmlDialog( Frame owner, String htmlPath, String title )
  47                                                              throws IOException
  48      {   super( owner, title, true
/*it's modal*/ );
  49  
  50          
// Set up an action listener that handles the form
  51          
// submission. [actionPerformed() is called when the
  52          
// user hits Submit or Cancel.
  53  
  54          pane.addActionListener
  55          (   new ActionListener()
  56              {   public void actionPerformed(ActionEvent event )
  57                  {   HtmlDialog.this.setVisible(false);
  58                      HtmlDialog.this.dispose();
  59                      data = ((HTMLPane.FormActionEvent)event).data();
  60                  }
  61              }
  62          );
  63  
  64          
// Load the input file from a CLASSPATH-relative directory
  65          
// specified in the HTMLPath argument, then import it into
  66          
// the HtmlDialog for display.
  67  
  68          URL loadFrom = getClass().getClassLoader().getResource(htmlPath);
  69          if( loadFrom == null )
  70          {   throw new IOException("Can't find $CLASSPATH/" + htmlPath );
  71          }
  72  
  73          pane.setPage( loadFrom );
  74          getContentPane().add( pane );
  75          pack();
  76      }
  77  
  78      
/** Get a {@link java.util.Properties} object that holds the
  79       *  data provided by the <input> elements on the form
  80       *  (or equivalent). The key is the element "name," the value
  81       *  is either the value specified in the attribute or the
  82       *  data the user typed.
  83       *
  84       *  @return the form data
  85       *  @throw java.lang.IllegalStateException if you try to call this
  86       *      method before the user submits the form. This can only
  87       *      happen if <code>data()</code> is called from a thread
  88       *      other than the one that issued the <code>popup()</code>
  89       *      request (which blocks).
  90       */

  91  
  92      public  Properties data()
  93      {   if( data == null )
  94              throw new java.lang.IllegalStateException(
  95                      "Tried to access data before form was submitted");
  96          return data;
  97      }
  98  
  99      
/** Add custom-tag processing to this dialog. Passes arguments through
100       *  to a contained HTMLPane's {@link HTMLPane#addTag addTag(...)} method.
101       *  A few out-of-the-box custom tags are already implemented for you
102       *  (see {#link HTMLPane}).
103       *
104       * @param tag
105       * @param handler
106       */

107      public void addTag( String tag, TagHandler handler )
108      {   pane.addTag(tag,handler);
109      }
110  
111      
/** Works just like {@link JFrame#show}, but returns true if the
112       *  dialog was closed with a normal close button. Returns false if
113       *  the user submitted the dialog by pressing a button specified with
114       *  the tag
115       *  <PRE>
116       *  <inputAction name=cancel ... >
117       *  </PRE>
118       *  (<a href=HTMLPane.java#inputAction>See</a>)
119       *  or just closed the dialog by clicking the "close" icon.
120       */

121      public boolean popup()
122      {   show();
123          if( data == null )  
// Dialog aborted with "close" icon.
124              return false;  
// Treat it like a cancel.
125  
126          String cancel = data.getProperty("cancel");
127          if( cancel == null )    
// no cancel button
128              return true;
129  
130          return !cancel.equals("true");
// True ==> cancel button pressed,
131                                        
// so return false.
132      }
133  
134      static private class Test
135      {
136          public static void main( String[] args ) throws Exception
137          {
138              JFrame owner = new JFrame();
139              owner.getContentPane().add( new JLabel("Parent Frame") );
140              owner.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
141              owner.pack();
142              owner.show();
143  
144              HtmlDialog d = new HtmlDialog(owner,
145                          "com/holub/ui/HTML/test/okay.html", "Test HtmlDialog");
146  
147              if( d.popup() )
148                  System.out.println("OK Pressed");
149              else
150                  System.out.println("Cancel Pressed");
151  
152              d.data().list( System.out );
153          }
154      }
155  }

 

Listing 2. okay.html

   1  <html>
   2  <head>
   3      <title>Titles are Ignored</title>
   4  </head>
   5  <body>
   6      <p>
   7      <form>
   8          <input type="hidden" name="hidden" value="hidden-field data">
   9          Type something: <input type="text" size="20" name="user-input"><br>
  10          <input type="submit" value="OK">
  11      </form>
  12      <br>
  13      <br>
  14  </body>
  15  </html>

 

جزئيات

اگر كمي دقيق‌تر به HTMLpane نگاه كنيم، HTMLpane به طرق مختلف سبب افزايش Jeditorpane مي شود:

HTMLpane، مخل خطاهاي معمولي را به com.holub.ui logger مي دهد نه اينكه exceptionها را throw كند. شما مي‌توانيد با ايجاد logger به اين پيامها متصل شويد.

من ظاهرا عناصر فرم را بهبود بخشيدم.

اينك دگمه‌هاي راديويي (radio buttons) و فيلدهاي ورود متن به طور صحيح در راستاي متن قرار دارند.

فهرست ايجاد شده با عنصر انتخابي فقط كمي عريض‌تر از متن محتوي آن است.

دگمه‌هاي راديويي وCheckbox ها، پس زمينه‌هاي شفاف دارند، لذا در متون پس زمينه و رنگهايي  كه خاكستري تيره نباشند، قابل رويت هستند (bgcolor = " # do do do" )

شما مي‌توانيد تگ‌هاي سفارشي بسازيد و ( handlerها را برايشان مشخص نماييد)، لذا رفتاري به سبك JSP ساده شده‌اي را به فرم‌هايتان مي‌افزاييد. اين ويژگي براي مشخص سازي تگ در ورودي HTML كه با مولفه Swing جايگزين شده است، مناسب مي‌باشد.

شما مي‌توانيد يك handler داده فرم محلي را در پاسخ به عمليات submit، cancel درخواست نماييد. كليك بر روي دگمه Submit سبب مي‌شود تا HTMLpane به جاي ارسال داده فرم به سرور راه دور، كدهايي را اجرا كند.

هايپرلينك‌هاي file:، http: استاندارد و URLها مديريت مي‌شوند، البته ساير پروتكل‌ها (نظير ftp:) پشتيباني نمي‌شوند. اگر كاربري بر روي هايپرلينك كليك كند، محتويات HTMLpane با ارجاعات URL جايگزين مي‌شود.

فقط هايپرلينك‌هايي بارگذاري مي‌شوند مرجع آنها با يكي از پسوندهاي زير خاتمه يابند:

توصيه من اينست كه به فايلهاي HTML يا كدهايي كه HTML را توليد مي‌كنند، لينك داشته باشيد.

فرض بر اين است كه همه URLهايي كه در مسيرشان و بعد از آخرين اسلش (/) داراي `.` نيستند به فايل index.html ضمني، اشاره دارند، بنابراين رشته index.html ضميمه مي‌شود.

همينطور index.html به URLهايي كه هيچ دايركتوري را مشخص نمي‌كنند يا در / خاتمه مي‌يابند، ضميمه مي‌شوند. (مثلا http://www.holub.com/directory، http://www.holub.com) فايل نسبي كه پسوند ندارد، نظير < a harf – “filename”> به عنوان فايل HTML شناخته نمي‌شود، لذا، پردازش نخواهد شد (شما مي‌توانيد متدي را در HTMLpane براي تغيير يك رفتار و يا تمام رفتارهاي مذكور قرار دهيد).

mailto: هايپرلينك‌هايي كه فقط پلات‌فرم ويندوز از آنها پشتيباني مي‌كند. اگر بخواهيد از mailto: در ساير پلات‌فرم‌ها استفاده كنيد با هشدار logged مواجه مي‌شويد.

صفت < a target = -blank …> سبب مي‌‌شود تا صفحه در پنجره خودش گشوده شود. پنجره يك فريم pack()ed است كه HTMLpane را نگاه مي‌دارد، لذا handlerهاي تگ سفارشي در popup كار مي‌كنند.

HTMLpane از ويژگي نگاشت ميزبان (host-mapping) پشتيباني مي‌كند. اين ويژگي بخش URL host را به نگاشت اختياري به URL ترجمه مي‌كند:

 HTMLPane my_pane;
//...
my_pane.add_host_mapping( "www.holub.com", "file:
//c:/src/test" );

 

يك لينك HTML شبيه دستورات زير است:

 <a href="http://www.holub.com/dir/foo.html">

 <a href="file://c:/src/test/dir/foo.html">

 اگر add-host-mapping(..) را پيش از يك‌بار فرا بخوانيد، همه نگاشتهايي كه مشخص نموديد، بكار مي‌روند. همه نمونه‌هاي HTMLFrame كه در نگاشتهاي host اشتراك دارند، شامل popupهاي ايجاد شده توسط هايپرلينك < a target = -blank …> مي‌باشند.

 

پردازش فرم

به طور عادي، هنگاميكه فرم HTML ارسال مي‌شود، Jeditorpane عمليات HTTP post يا GET را بر روي سرور راه دور اجرا مي‌كند و سرور داده‌هاي مرتبط با عناصر فرم را به صورت زوج name=value عبور مي‌دهد.

HTMLpane رفتار پيش فرض پردازش فرم را به گونه‌اي تغيير مي دهد كه فرم به جاي ارسال به سرور موجود در شبكه، به برنامه فعلي ارسال شود و فقط Java.awt.event.ActionListener را با فراخواني add ActionListener(…) به HTMLpane مي‌افزايد. متدaction performed () مربوط به Listener هنگامي كه كاربر دگمه submit را مي زند، فرا خوانده مي‌شود.

در واقع آبجكت Action Event مربوطه، يك نمونه كلاس HTMLpane.Form Action Event است و شما مي‌توانيد داده‌هاي ارسالي را به طور مستقيم از اين آبجكت event به دست آوريد.

در اينجا مثالي از handler ساده ارسال فرم آمده است كه همه داده‌هاي فرم را بر روي خروجي استاندارد چاپ مي‌كند و سپس success page را در فريمي كه براي نگهداري فرم بكار مي رود، نمايش مي‌دهد:

 

pane.addActionListener
(   new ActionListener()
    {   public void actionPerformed( ActionEvent event )
        {
            FormActionEvent act = (FormActionEvent)event;

            
// Print the "method=" and "action=" arguments of the
            
// original <form...> tag.

            System.out.println("\n"+            act.getActionCommand() );
            System.out.println("\t"+"method=" + act.method() );
            System.out.println("\t"+"action=" + act.action() );

            
// Print all the data associated with an <input...>
            
// element or equivalent.

            act.data().list( System.out );
            System.out.println("");

            
// Replace the form that was just submitted
            
// with the the "success page".

            try
            {   act.source().setPage
                ( new URL(
                    "file:
//c:/src/com/holub/ui/HTML/test/submit.html")
                );
            }
            catch( Exception e )
            {   e.printStackTrace();
            }
            System.out.println("");
        }
    }
);

به محض اينكه هر گونه handler فرم را بيفزاييد، همه ارسال فرم آمده است كه همه داده‌هاي فرم به جاي ورود به URL مقصد، در handler وارد مي‌شوند و در صورتي كه مايل باشيد handler مي‌تواند داده‌ها را در وب قرار دهد. Listenerها نيز آبجكت Form Event مشابهي را عبور مي دهند.

Listenerهاي چندگانه شيوه شي‌گرا براي ساخت واسط كاربر را تسهيل مي‌نمايند. آبجكت كنترلي، واسط كاربر مركب را با درخواست چند آبجكت تجاري كه در HTML به نمايش درآمده مشاركت دارند، مي‌سازند. اين مشاركت در صورتي كه آبجكت بخواهد اطلاعاتي را از كاربر دريافت كند، حاوي تگ‌هاي <input …> است. سپس اين آبجكت‌ها به submit گوش فرا مي‌دهند و هنگاميكه submit روي دهد، هر آبجكت فقط اطلاعات مورد نظر را از داده‌هاي فرم parse مي‌كند. به عبارتي اگر آبجكتي با چند عنصر در HTML مرتبط شود، مقدار آن عنصري كه كاربر تايپ كرده، به وي بازمي‌گرداند. بدين ترتيب، HTMLpane، ساير Contributorهاي فرم و آبجكت‌هاي كنترلي نيازي به دانستن نحوه پياده‌سازي آبجكت مشاركتي ندارند.

 

تگ‌های سفارشي

شما مي‌توانيد تگ‌هاي سفارشي را در ورودي HTML همانند تگ سفارشي JSP، تعريف نماييد. تنظيم تگ جديد با فراخواني some-pane-add-tag(…) و عبور دادن آبجكتي كه handler تگ را شناسايي كند، صورت مي‌گيرد.

از آنجائيكه Jeditor pane، اساس كلاس فعلي است، نمي‌توانيد تگ سفارشي به سبك فضاي نام تعريف كنيد: نامي نظير < holub:my-tag …> (كه حاوي : است) قابل درك نيست، لذا بايد از استفاده كنيد.

همچنين، پياده‌سازي فعلي تگ‌هاي سفارشي به عنصر اجازه نمي‌دهد كه محتوا داشته باشد. مشكل اين است كه parser پيش فرض سبب ارتباط بين تگ‌هاي غير HTMl نمي‌شود يعني < foo >، < /foo> به عنوان دو تگ كاملا مستقل كه هيچگونه ارتباطي بين آنها وجود ندارد، در نظر گرفته مي‌شوند. تنها راهكار اين است كه به طور اثربخشي parser بازنويسي شود كه به كار زيادي نياز دارد.

تگ‌هاي سفارشي از پيش ساخته شده متعددي تهيه مي‌شوند. اگر از HTMLpane(boolean)Constructor استفاده كنيد، پشتيبان اين تگ‌ها، به طور خودكار نصب مي‌شود در غير اينصورت مي‌توان با ارسال يكي از پيامهاي زير به آبجكت HTMLpane، از تگ پيش ساخته پشتيباني نمود:

 

pane.add_tag( "size"        , new Size_handler()            );
pane.add_tag( "input_action", new Input_action_handler(this));
pane.add_tag( "input_number", new Input_number_handler()    );
pane.add_tag( "input_date"  , new Input_date_handler  ()    );

 

تگ‌ها بدين شكل نوشته مي‌شوند:

<size height=400 width=400>

 

اندازه pane بر حسب پيكسل بدين صورت تعريف مي‌گردد:

<input_action name="myName" value="label on button">

 اين تگ دگمه‌اي به سبك Submit را درج مي‌كند و سبب مي‌شود تا پيام‌هاي actionperformed(…) به همه آبجكت‌هاي Action Listener ثبت شده، ارسال شود.

به جز داده فرمي كه توسط ساير تگ‌هاي سفارشي تهيه مي‌شود، هيچيك از داده فرم‌‌هاي عادي در آبجكت Form Action Event كه به listener عبور داده مي‌شوند، وجود ندارند. لذا در ابتدا، اين تگ براي دگمه Cancel سودمند است. متدهاي method()، action() از Form Action Event، آبجكت preperties را باز مي‌گرداند. اين آبجكت زوج نام / مقدار را براي اين دگمه نگاه ميدارد (نام با name = attribute و مقدار با رشته true مشخص ميشود).

 

<input_number name = "fred"value = "0.0” min = "0” max = "100” precision = "2” size = "20”>

 

اين شبيه تگ <input type = text> است، اما ورودي‌هاي آن مقدار عددي در محدوده min <= N <= max است. اگر كاربر بخواهد مقدار خارج از محدوده وارد كند، كنترل يك پنجره‌اي شبيه tooltip را مي‌گشايد و به كاربر تذكر مي‌دهد. اين از tooltipهاي عادي نيز پشتيباني مي‌كند و محدوده قابل قبول مقادير را به وي مي‌گويد. صفت اختياري Size نيز به عرض فيلد است.

 

<input – data name = "fred" value = “10/15/03” size = "20” >

 

اين فيلد ورود تاريخ localize است كه كنترل اعتبار داده‌ها را در هنگامي كه Enter را مي‌زنيد، انجام مي‌دهد. اكثر فرمت‌هاي تاريخ قابل درك هستند و با گشوده شدن كادر محاوره به شما امكان مي‌دهند تا تاريخ‌هاي مورد نظر را از تقويم گرافيكي انتخاب كنيد. اگر مقدار اوليه موجود باشد، بايد تاريخ را در يكي از فرمتهاي استاندارد مشخص نمايد (ورود رشته خالي مجاز نيست). اگر Value = attribute مشخص نشود، تاريخ روز به عنوان مقدار اوليه در نظر گرفته مي شود. صفت اختياري سايز، به عرض فيلد است.

اگر تگ‌هاي سفارشي پيش ساخته مناسب نباشند، شما مي‌توانيد تگ‌هاي سفارشي خود را با پياده‌سازي واسط TagHandler (ليست 3) بسازيد و سپس  پياده‌سازي‌تان را با فراخواني addTag()، همانگونه كه براي handlerهاي پيش ساخته انجام مي‌دهيد، ثبت نماييد. من در مثال زير يك handler ساختم و ثبت نمودم كه فقط صفات عنصر <myTag some – attribute = “hello”> را چاپ مي‌كند. آرگومان صفات به آبجكت properties حاوي همه صفات تگ اشاره دارد:

pane.addTag
(   "myTag",
      new TagHandler()
      {   public JComponent
          handleTag(HTMLPane source, Properties attributes )
          {   attributes.list( System.out );
              return null;
          }
      }
);

 

 

با دسدور HTML ورودي <myTag value="hello world">، عبارت hello world بر روي System.out به نمايش گذاشته مي‌شود و ورودي درخواست مي‌شود. با استفاده از دستور handleTag(…) به شكل زير مي‌توانيد به attribute ها دسترسي پيدا كنيد.

 

String contents = attributes.getProperty("value");

 

Handler مربوط به آرگومان سول به شيي HTMLPane اشاره مي كند كه تگهاي سفارشي در آن نشان داده ميشود. با استفاده از آن ميتوانيد تگي ايجاد كنيد كه نماي فيزيكي HTMLPane را تغيير دهد. به مثال زير توجه كنيد:

 

pane.addTag
(  "size",
    new TagHandler()
    {  public JComponent
       handleTag(HTMLPane source, Properties attributes)
       { source.setPreferredSize
         (  new Dimension
            ( Integer.parseInt(attributes.getProperty("width")),
              Integer.parseInt(attributes.getProperty("height"))
            )
         );
         return null;
       }
    }
);

 

هيچيك از مثالهاي مذكور در HTMLpane نشان داده نمي‌شود، زيرا اگر handler تگ سفارشي مقدار تهي (null) برگرداند، تگ پس از اجراي handler از ورودي خارج خواهد شد. يك تگ سفارشي را كه محل Jcomponent را نگاه مي دارد، تعريف مي‌كنيم. اين تعريف به سادگي با بازگرداندن Jcomponent از handlerTag(…) صورت مي‌گيرد.

كد زير تگ <hello-button> را مي‌سازد. اين تگ به صورت دگمه به نمايش در‌مي‌آيد و با كليك بر روي آن hello world در كنسول به نمايش در‌مي‌آيد:

 

Button helloButton = new JButton( "Hello World" );
hello.addActionListener
(   new ActionListener()
    {   public actionPerformed(ActionEvent e)
        {   System.out.println("Hello World");
        }
    }
);

pane.addTag
(  "hello-button",
    new TagHandler()
    {   public JComponent
        handleTag(HTMLPane source, Properties attributes)
        {   return helloButton;
        }
    }
);

 

سرانجام، تگ سفارشي مي‌تواند در ارسال داده‌هاي فرم به برنامه، در هنگاميكه كاربر دگمه Submit را مي‌زند، مشاركت نمايد. استراتژي اصلي، بازگرداندن Jcomponentي است كه واسط TagBehavior را پياده‌سازي مي‌كند (ليست 4).

ليست 5 نحوه بكارگيري واسط را تشريح مي‌كند. آبجكت Contributing Text در فرم به صورت كنترل متن (GtextField) ظاهر مي‌شود. هر آنچه كه كاربر تايپ كند، در آبجكت properties داده‌هاي فرم كه از متد Form Action Events data() با زدن دگمه Submit مي‌گيريد، ظاهر خواهد شد. آرگومان نام در Contributing Text Constructor، مقدار كليد را مشخص مي‌كند، آرگومان initial value براي فيلد متني به كار مي رود، اما رشته‌اي كه در فيلد متني به نمايش درمي‌آيد، مقدار مرتبط با كليدي است كه از داده‌هاي فرم مي‌گيريد.

متد reset() با زدن دگمه Reset (فرم با عنصر input type = "reset") فراخواني مي‌شود. اين متد در ليست 5 كنترل متن را به مقدار اوليه باز مي‌گرداند.

متد destory وقتي فرم متوقف مي‌شود، فراخواني خواهد شد. در اين مثال، عملي را انجام نمي دهد، اما مي‌توانيد از ان براي ذخيره‌سازي داده‌هايي كه كاربر در پايگاه داده‌ها وارد مي‌كند، استفاده نماييد.

سرانجام، متد getFormData() توسط HTMLpane فراخواني مي‌شود. اين روش بايد رشته‌اي از Key = value را بازگرداند.

سپس Component را با HTMLpanel با كمك addTag() يكپارچه سازيد. مثال زير تگ < Text> را مي‌سازد. اين تگ صفات نام و مقدار را مي‌گيرد. Handler آبجكت Contributing Text را مي‌سازد و آن را با مقادير صفات معرفي مي‌كند. كد مربوطه در ذيل آمده است:

 

// Create the tag <text name="xxx" value="hello world">.
// The tag is replaced by a ContributingText object, initialized
// with the string specified in the value attribute. The
// user may modify this string, and whatever string is in the
// text control when the Submit button is pressed is returned
// in the form data.

pane.addTag
(   "text",
     new TagHandler()
     {   public JComponent
         handleTag(HTMLPane source, Properties attributes)
         {
            return new ContributingText( attributes.getProperty("name"),
            attributes.getProperty("value") );
         }
     }
);

 

براي مشاهده مثال كامل آن، كد (ليست 6) را براي كلاس Input Action Handler و پياده‌سازي تگ <input – action …> آورده‌ام.

 

Listing 3. TagHandler.java

   1  package com.holub.ui.HTML;
   2  
   3  import java.util.Properties;
   4  
   5  import javax.swing.JComponent;
   6  import java.util.Properties;
   7  
   8  
/** Define a custom tag handler. See the documentation {@link HTMLPane}
   9   *  for an in-depth explanation of how to use this interface.
  10   */

  11  
  12  public interface TagHandler
  13  {
  14      
/**...*/
  15      JComponent handleTag( HTMLPane source, Properties attributes);
  16  }

 

 

Listing 4. TagBehavior.java

   1  package com.holub.ui.HTML;
   2  
   3  
/** This interface provides the wherewithal for a <code>JComponent</code>
   4   *  to act like an HTML <input> tag in an {@link HTMLPane}.
   5   *  <p>
   6   *  Note that the methods of this interface are lower level
   7   *  than those of the Provider. For example, the <code>destroy()</code>
   8   *  method will be called every time the form shuts
   9   *  down for whatever reason.
  10   *
  11   *  @see TagBehavior.Adapter
  12   */

  13  
  14  public interface TagBehavior
  15  {  
/** This method is called to get <em>name=value</em> pairs used
  16       *  as form data when the user hits the Submit button.
  17       *  @return a list of one or more newline-delimited
  18       *      name=value pairs or an empty string (<code>""</code>)
  19       *      if there is no data to add. These pairs are
  20       *      added to the form data.
  21       */

  22      public String getFormData();
  23  
  24      
/** This method is called when the user hits the Reset button.
  25       *  It should restore the object to its initial state.
  26       */

  27      public void   reset();
  28  
  29      
/** This method is called when the user moves on to the next
  30       *  page or the window containing the form is shut down.
  31       *  Use this hook to release any global resources that
  32       *  the TagBehavior object might be using.
  33       */

  34      public void   destroy();
  35  
  36      
/** A convenience class, implements {@link TagBehavior} with
  37       *  methods that do nothing. You can extend this class instead
  38       *  of implementing {@link TagBehavior} when you don't need
  39       *  to override all the methods of the interface.
  40       */

  41      public static class Adapter
  42      {   public String getFormData(){ return ""; }
  43          public void   reset();
  44          public void   destroy();
  45      }
  46  }

 

 

Listing 5. ContributingText.java

   1  import javax.swing.*;
   2  import java.awt.*;
   3  
   4  import com.holub.ui.HTML.*;
   5  
   6  class ContributingText  extends JTextField
   7                          implements com.holub.ui.HTML.TagBehavior
   8  {    private final String value;
   9       private final String name;
  10  
  11       public ContributingText(String name, String initialValue)
  12       {   super(initialValue);
  13           this.name   =name;
//Initialize from attributes specified in the tag.
  14           this.value  =initialValue;
  15       }
  16  
  17       public void reset()        
// Called when the Reset button is hit.
  18       {   setText(value);        
// Restores the initial value.
  19           invalidate();
  20       }
  21  
  22       public void destroy(){
/* nothing to do */ }
  23  
  24       public String getFormData ()      
// Add an attribute to return
  25       {   return name + "=" + getText();  
// to the form processor, as if
  26       };                                  
// this component was an <input...>.
  27  
  28      
// Normal JTextField overrides to make the thing display at
  29      
// a reasonable size.
  30  
  31       public Dimension getPreferredSize(){ return new Dimension(150,20);}
  32       public Dimension getMinimumSize()  { return getPreferredSize();   }
  33       public Dimension getMaximumSize()  { return getPreferredSize();   }
  34  }

 

Listing 6. InputActionHandler.java

   1  package com.holub.ui.HTML;
   2  
   3  import java.util.Properties;
   4  import javax.swing.*;
   5  import java.awt.*;
   6  import java.awt.event.*;
   7  
   8  
   9  
/**...*/
  10  
  11  public class InputActionHandler implements TagHandler
  12  {
  13      private final HTMLPane pane;
  14      InputActionHandler( HTMLPane pane ){ this.pane=pane; }
  15  
  16      
// This method is called when the <inputAction...> tag is
  17      
// first processed, when the HTML input is imported into
  18      
// the HTMLPane.
  19      
//
  20      public JComponent handleTag(HTMLPane source, Properties attributes)
  21      {
  22          final String name  = attributes.getProperty("name");
  23          final String value = attributes.getProperty("value");
  24  
  25          
// Pressed must be declared final because it's accessed
  26          
// from an inner-class object. Implement it as a one-element
  27          
// array so that it can nonetheless be modified.
  28          
//
  29          final boolean pressed[] = new boolean[]{ false } ;
  30  
  31          
// This class is the JComponent that will appear on the
  32          
// screen in place of the tag.
  33          
//
  34          class ButtonTag extends JButton implements TagBehavior
  35          {   ButtonTag(String text){ super(text); }
  36              public void reset()
  37              public void destroy()
  38  
  39              
// Called by the HTMLPane when it assembles the
  40              
// Properties object that holds the form data
  41              
// in order to send that data to its listeners.
  42              
//
  43              public String getFormData()
  44              {  
  45                  
// If the button is pressed, the form data holds
  46                  
// a name=value pair that holds the value "true"
  47                  
// The name is specified in the tag.
  48                  
  49                  return  (name == null)
  50                          ? ""
  51                          : (name + "=" + pressed[0])
  52                          ;
  53              }
  54          }
  55  
  56          ButtonTag proxy = new ButtonTag(value);
  57          proxy.setAlignmentY( HTMLPane.BASELINE_ALIGNMENT );
  58  
  59          
// Set up to handle the button click. Set the "pressed"
  60          
// state true and call HTMLPane's handleInputActionTag(...)
  61          
// method, which gathers up the form data and sends it
  62          
// to the HTMLPane's listeners with an actionPerformed(...)
  63          
// message. The form data for the "proxy" is fetched
  64          
// from the ButtonTag object's getFormData(...) method,
  65          
// declared about 10 lines up.
  66          
//
  67          proxy.addActionListener
  68          (   new ActionListener()
  69              {   public void actionPerformed( ActionEvent e )
  70                  {   pressed[0]=true;
  71                      pane.handleInputActionTag(name);
  72                  }
  73              }
  74          );
  75          return proxy;
  76      }
  77  }

 

ثبت log

HTML Panel، error loggingها و هشدارها را در com.holub.ui logger ثبت مي‌كند. براي مشاهده پيام‌هاي برنامه‌هايي كه از HTML panel استفاده مي‌كنند، فايل JAVA-HOME/jre/lib/logging.properties را تغيير دهد و ويژگي Java.util.logging.console Handler.level، .level را به صورت ALL تنظيم كنيد. شما مي‌توانيد از كد زير براي فعال‌سازي logging در برنامه استفاده كنيد:

import java.util.logging.*;
//...
static
{   Logger  log = Logger.getLogger("com.holub.ui");
    Handler h = new ConsoleHandler();
    h.  setLevel(Level.ALL);
    log.setLevel(Level.ALL);
    log.addHandler(h);
}

 

مي‌توانيد logging را با مشخص‌سازي Level.OFF به جاي Level.ALL، غير فعال سازيد.

 

مشكلات مشهور

اين كلاس مبتني بر Jeditorpane، است كه از HTML parser استفاده نمي‌كند. مشكلات Parser شركت سان ميكروسيستم كه در ذيل آمده است، در پياده‌سازي فعلي رفع نمي‌شوند:

1. parser كند است.

2. از CSS پشتيباني نمي‌شود. Style بي‌مصرف هستند.

3. هيچيك از تگ‌هاي HTML4 يا HTML، مديريت نمي‌شوند.

4. Parser جداول را به خوبي مديريت نمي‌كند. Nesting جدول‌هاي ساده خوب انجام مي‌شود اما نوع پيچيده اينگونه نيست.

5. از تگ‌هاي < applet …> پشتيباني نمي‌شود. اما از تگ‌هاي < abject…> پشتيباني مي‌شود.

بنابراين از نوع اخير براي جاسازي اپلت‌ها در فرم استفاده كنيد. بدين منظور به Java plug-in docs مراجعه نماييد.

6. Parser جاوا 4/1، <input type = submit name = x value = y> را به خوبي مديريت نمي‌كند. البته اين مشكل در جاوا 5/1 رفع شده است. به عبارتي رشته name = value در داده‌هاي فرم به طور صحيح قرار ندارد، لذا نمي‌توانيد از فيلدهاي ورودي type = submit در يك فرم منفرد استفاده كنيد. از عناصر <input type = button> و <button> نيز پشتيباني نمي‌شود.

كلاس مبناي Jeditor pane، تگ‌هاي اسكريپت (يا جاوا اسكريپت) را درك نمي‌كند، و فقط به صورت متن معمولي محتويات همه عناصر اسكريپت را نمايش مي‌دهد. متاسفانه، HTML توليد شده توسط Javadoc از nest-script-in-comments تبعيت نمي‌كند و نمي‌توانيد به سهولت از Jeditorpane به عنوان مرورگر مستندسازي جاوا استفاده كنيد.

Jeditorpane، فريم‌ها را در هنگام سفارشي‌سازي به خوبي انجام نمي‌دهد در ظاهر فريم‌ها كار مي‌كنند، اما صفحات نمايش داده شده با فريم‌ها در صورتي پردازش مي‌شوند كه از Javax.swing.Jeditorpane استفاده شود در نتيجه نمي‌توانيد از فريم‌هاي HTML استفاده كنيد، با اين وجود، مي‌توانيد نمونه‌هاي متعدد HTMLpane را بسازيد و آنها را با كمك Gird BagLayout يا Grid Layout (يا ساير كانتينرها) درون Jpanel مرتب سازيد.

من اميدوارم كه حداقل برخي از رفتارهاي Editorkit اصلاح شود و حدسم اين است كه بيشتر مشكلات ساختاري رفع شوند.

 

نكاتي در زمينه طراحي

كلاس‌هاي سازنده واسط كاربر Client-side به صورت پراسيجر و عمومي هستند، لذا نمي‌توانيد accessorها و mutatorها را حذف كنيد، فقط مي‌توانيد تعدادشان را كاهش دهيد.

HTMLpane در صورت لزوم آبجكت‌هاي properties كه شامل داده‌هاي فرم باشند، نشان مي‌دهد، اما هنوز نحوه پياده‌سازي HTMLpane مشخص نيست (مبتني بر Jeditorpane). داده‌هاي فرم ظاهر شده در ء,حشدث جريان مي‌يابد و به صورت ورودي و به صورت خروجي نمايش داده مي شود.

آنالوگ خوبيدر كلاس‌هاي كلكسيون جاوا وجود دارد. شما مي‌توانيد آبجكت‌هايي را در كلكسيون قرار دهيد، اما در واقع نمي‌دانيد كه كلكسيون چگونه كار مي‌كند. پياده‌سازي كلكسيون مخفي است. كلكسيون فقط از داده‌هاي تهيه شده مراقبت مي‌كند و در واقع چيزي درباره اين داده‌ها نمي‌داند الگوي طراحي در اينجا Memento (يادآور) است. طراحي پراسيجري پنهان‌سازي داده‌ها را با پياده‌سازي شي‌گرا اشتباه نگيريد. آيا مي‌توانيد پياده‌سازي را بدون تاثيرگذاري بر كلاسهاي كلانيت تغيير دهيد؟ كدي كه به Jeditorpane در HTMLpane افزودم، اين آزمون را پشت سر گذاشت.

اتخاذ تصميم براي استفاده از action Listener، Event devivative مثال خوبي در اين زمينه است. مكانيزم Action Listener روشي است كه مي‌توان از آن در ساير مولفه‌هاي swing بهره جست. گرچه مي‌‌توانستم انواع مختلف آبجكت Observer كه نياز به data() accessor را مرتفع مي‌سازند، معرفي كنم، اما به نظر مي رسد اين ايده خوبي نباشد.

 

نتيجه

من از طرح HTML در چند برنامه استفاده نمودم. عيب عمده شيوه HTML اين است كه كاربر نمي‌تواند تجربه خوبي با واسط كاربر مبتني بر HTML به صورت client-side يا server-side داشته باشد. استفاده از HTML بر روي Client-side موجب صرفه‌جويي در زمان ساخت واسط كاربر مي‌شود. ساخت واسط كاربر كه بتواند با حداقل مشكلات در Client-side، Server-side حركت كند، امكان‌پذير است.

 

منابع

 

Copyright 2004 IDG News Service.All right reserved.
Copyright 2004, PC World Iran, All rights reserved.