深入浅出Web Page Editor

1 简介

Web Page Editor(以下简称WPE)是eclipse开源子项目WTP的一部分。其插件名全称为:org.eclipse.jst.pagedesigner。Java EE开发环境会自带这个插件包。如果没有,也可以到http://download.eclipse.org/webtools/downloads/页面下载。它主要提供一个WYSIWYG的Web开发环境,支持控件拖拽,CSS可视化编辑等等。由于它是开源的,所以一般常常可以用来作为开发Web开发RCP程序的基础。它本身也预留了插件接口,以方便开发人员扩充自定义插件,以供拖放。

目前这个版本1.3.2的Page Editor还残留着严重的排版问题,几乎所有的块级元素都会一股脑儿的顺次排下来,而不去理会它们的CSS属性中是否有positon:absolute。但是这不妨碍我们对它现有的结构进行分析。

要深入了解WPE,就不得不提到GEF了,本文假定你已经对GEF有过了解。如果不明白GEF原理,可以看看八进制写的GEF入门文章http://bjzhanghao.cnblogs.com/category/36197.html

听说写教程时标题不要太俗,但也一时想不到什么好的了,就这么地吧。

2  界面结构

打开JavaEE开发环境,随便新建一个Web工程,再随便新建一个jsp文档。右键此文档,选择用Web page editor打开。可见如下视图。

图 2‑1

较新版本的Eclipse会自带一个Plugin spy插件。按下Shift + Alt + F1可以看到当前工作环境中的主要Editor插件。这里顺便提一下,在Eclipse的概念里,主Editor是只有一个的,其它小区块以View的形式环绕在Editor四周。

Plugin spy的效果如下图所示:

图 2‑2

可见当前界面的Active Editor为HTMLEditor,也就是图 2‑1中红色的部分。它主要分为两个区,用紫色区域表示。上面是为可视化编辑区,对应类SimpleGraphicalEditor;下面为文本编辑区,对应类StructuredTextEditor。

上面可视化编辑区的SimpleGraphicalEditor直接继承自GraphicalEditorWithFlyoutPalette,如果你对GEF有过了解的话,对这个类应该不会陌生。它与GraphicalEditorWithPalette的区别在于,前者的Palette托盘可以通过点选一个小箭头按钮决定是否显示出来,而后者是固定的。

SimpleGraphicalEditor也包括两个主要部分,在图 2‑1中用橙黄色表示。左侧的Paletteu部分为PaletteViewerPage,右侧的编辑区为IHTMLGraphicalViewer。

3 MVC模型结构

WPE是一个典型的GEF框架应用 。而GEF又是个典型的MVC三层结构,所以我们有必要对GEF三层模型在WPE中的具体实现进行深入了解。

View层对应到两个类:负责图 2‑1右侧橙色框的HTMLGraphicalViewer和左侧橙色框的PaletteViewerPage。后者是封装好的结构,我们不必过多纠缠。关注点主要在前者。

HTMLGraphicalViewer是作为SimpleGraphicalEditor的一个类成员存在的,其初始化过程在SimpleGraphicalEditor. createGraphicalViewer()中可以看到如下初始化代码:

protected void createGraphicalViewer(Composite parent)
{
       _viewer = IHTMLGraphicalViewer.Factory.createGraphicalViewer(this);
       Control control = _viewer.createControl(parent);
       PlatformUI.getWorkbench().getHelpSystem().setHelp(control,  PDPlugin.getResourceString("SimpleGraphicalEditor.help.id")); //$NON-NLS-1$
       setGraphicalViewer(_viewer);
       configureGraphicalViewer();
       hookGraphicalViewer();
       initializeGraphicalViewer();
       initializeContextMenu();
}

在SimpleGraphicalEditor.initializeGraphicalViewer()方法中我们可以发现,HTMLGraphicalViewer的EditPartFactory被设置到了HTMLEditPartsFactory上。

HTMLEditPartsFactory组织了一张由Model层到Control层EditPart的映射表。

其核心部分源代码如下:

if (model instanceof Node)
{
       Node node = (Node) model;
       if (node.getNodeType() == Node.DOCUMENT_NODE)
       {
               part = new DocumentEditPart();
       }
       else if (node.getNodeType() == Node.ELEMENT_NODE)
       {
               part = new ElementEditPart();
       }
       else if (node.getNodeType() == Node.TEXT_NODE
                       || node.getNodeType() == Node.CDATA_SECTION_NODE)
       {
               part = new TextEditPart();
       }
}

它的核心思想就是根据Model层模型的类型来生成Control层不同的EditPart。总的来说,WPE中Model层主要是DOMDocumentForJSP、ElementImplForJSP和TextImplForJSP。它们的继承关系分别如下:

它们具有不同的getNodeType()返回值,依此值的不同,它们与Control层的EditPart类有如下的对应关系:

Model层 Control层(EditPart)
DOMDocumentForJSP DocumentEditPart
ElementImplForJSP ElementEditPart
TextImplForJSP TextEditPart

对于Model层而言,DOMDocumentForJSP是顶层的Model,它代表了整个文档。而一个控件至少包含了一个ElementImplForJSP,也可能包含了TextImplForJSP。ElementImplForJSP一般会作为DOMDocumentForJSP的Childern而存在。

<a href="http://www.thankcreate navigate to this website.com/wp-content/uploads/2011/07/EditParts.jpg” rel=”lightbox[312]”>

对于Control层而言,ScalableRootEditPart是顶层的EditPart。它只是一个逻辑意义上的存在,不与Model层关联。实际上,它只会把一个DocumentEditPart的实例作为它唯一的子结点。

对于不同的控件,它们对应的Model层和Control层的类都是ElementImplForJSP和ElementEditPart。区别在于不同控件的Tag、TagConverter、FigureHandler和WidgetProvider不一样,下面会对这种特异性作详细介绍。

4 各控件的特异性

4.1     Tag

Tag顾名思义,即一个控件的标签。a link的tag即a, trailer的tag即trailer。它是一个控件最重要的标识,很多地方会对这个标识作判断,然后进行下一步操作。

4.2     Tagconverter

ElementEditPart类有一个ITagConverter类型的成员。它是一个转换器,把一个Element(M层)转换到另一个Element上。以Trailer的专属转换器TrailerTagConverter为例,有如下继承关系:

在AbstractTagConverter中可以看到如下定义

private IDOMDocument _targetDocument;
private Element _hostElement;
private Element _resultElement;

其中hostElement和resultElement别代表了转换前和转换后的Element(M层Element,实际上就是ElementImplForJSP)。

在TrailerTagConverter.doConvertRefresh()中可以看到如下代码:

       protected Element doConvertRefresh()
       {
               _emptyContainer = ConverterUtil.isEmptyContainer(getHostElement());
               if (_emptyContainer)
               {
                       // A trailer element contains a div element and a image element.
                       Element div  = createElement(IHTMLConstants.TAG_DIV);
                       Element img = createElement(IHTMLConstants.TAG_IMG);
                       div.appendChild(img);
                       // copy host element to the new div element
                       JSFConverterUtil.copyAllAttributes(getHostElement(), div, null);
                       JSFConverterUtil.copyAllAttributes(div, img, null);
                       return div;
               }
               return super.doConvertRefresh();
       }

Trailer控件的Model层体现原来是个Tag为trailer的ElementImplForJSP, 但是如果执行了doConvertRefresh,转换器就会新建一个以img作为子结点的div。并且把原有的trailer标签的属性拷贝到新的div和img上。需要注意的是,这种转换不是原地覆盖型的,而是转换到前面所提到的_resultElement上。

4.3     FigureHandler和WidgetProvider

这两个东西似乎是eclipse的WTP(web tools project)特制的绘图机制。FigureHander相当于对Figure的一个封装。而WidgetProvider承担了更底层的绘图任务。当一个控件加入到文档中时,这个控件自身的ElementeEditPart::refresh(false)方法将会被调用。进而深入到createFigureHandler函数中,创建一个IfigureHandler:

      private static IFigureHandler createFigureHandler(Element ele) {
               String tag = ele.getTagName();
               if ("input".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new InputFigureHandler();
               } else if ("select".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new SelectFigureHandler();
               } else if ("img".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new ImgFigureHandler();
               } else if ("object".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new ObjectFigureHandler();
               } else if ("trailer".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new TrailerFigureHandler();
               } else if ("textarea".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new TextareaFigureHandler();
               } else if ("br".equalsIgnoreCase(tag)) { //$NON-NLS-1$
                       return new BRFigureHandler();
               } else if (!HTMLUtil.isVisualHtmlElement(tag)) {
                       return new HiddenFigureHandler(getSharedHTMLImage(ele));
               } else {
                       return new DefaultFigureHandler();
               }
       }

这个函数会根据element不同的tag,创建不同的FigureHander。下一步紧接着会执行这个新建的hander的update(Element, CSSFigure)方法:

public void updateFigure(Element node, CSSFigure oldFigure) {
               setCurrentFigure(oldFigure);
               ICSSWidgetProvider provider = initializeWidgetProvider(node);
               oldFigure.setCSSStyle(provider.getCSSStyle());
               oldFigure.setFixedLayoutManager(new CSSWidgetLayout(oldFigure, provider));
       }

这个方法会设置此hander对应的CSSFigure(继承了Figure)。并且为该hander初始化WidgetProvider。至于WidgetProvider内部,它可以指定图片来源,其 paintFigure方法更是可以像下面一样直接指定绘图方式:

  public void paintFigure(Graphics g, Rectangle rect) {
               if (_image != null) {
                       g.drawImage(_image, 0, 0, _imageWidth, _imageHeight, rect.x,
                                       rect.y, rect.width, rect.height);
               }
       }

5  残留问题

上面的paintFigure只是相对于已经经过排版的Graphics对像g来进行绘图。但是g这个对象是怎么被排版的,还需进一步发掘。

13 thoughts on “深入浅出Web Page Editor

  1. 你好,我很想开展Web Page Editor的二次开发,但是对于环境准备比较困惑,不知道怎么去准备eclipse的插件开发环境,也不清楚Web Page Editor的代码和依赖的lib包如何解决。请问能给一个指引吗?谢谢。

    1. 我当时用的eclipse 插件开发环境是clipse for rcp 3.7
      web page editor最主要的部分是org.eclipse.jst.pagedesigner
      可以在:pserver:anonymous@dev.eclipse.org:/cvsroot/eclipse这个cvs里整个拖出来
      依赖的部分的话,其实如果你不作大改动,直接装个wtp包(wtp的官网在:http://www.eclipse.org/webtools/)就行了,就至少可以跑起来
      但是为了扩展,至少要用到的有:
      org.eclipse.wst.css.core
      org.eclipse.wst.html.core
      org.eclipse.wst.sse.core
      这些都可以在刚刚那个cvs路径里直接拖下源码

  2. 非常感谢你的回答
    我是想用他去构建一个表单编辑器,如果整个wtp包拿来引用,就太庞大了。有160多M。能否指导一下,如何可以引用最小的包就可以进行扩展呢?非常感谢。期待你的回答。

    1. 引用最小的包的话,就围绕着org.eclipse.jst.pagedesigner为核心来添加吧。
      并不是说安装了wtp包,最终的导出程序里就会有整个的wtp包。这里的wtp包可能更多的有一种sdk的味道。
      要说具体的依赖,打开这个插件的plugin.xml,看看他里面的dependencies里的required plugin-ins就行了。不过其实我们并不需要特别认真的关注这个,因为eclipse rcp开发环境会自动帮你递归查询各个工程插件的依赖,并自动添加到工程配置中的。

      (*)真正在最后export出的目标程序带有哪些包,是由你的product文件的dependencies决定的。
      我的web page editor程序目前在100M左右,还有很大的压缩空间。

  3. 兄弟,真心加qq 1365276552 有几个问题急问
    :pserver:anonymous@dev.eclipse.org:/cvsroot/eclipse 这里用cvs 说connection reset,请问还有哪能搞到源码,下了个wtp的包里有,但是里面又没有plugin.xml文件,求指导

  4. 兄弟,另外我从官网上下了整个wtp包,里面有pagedesigner3个源码包,但是缺少plugin.xml文件,我从pagedesigner的jar包里考了一个过来,运行的时候,可以以web page editor方式打开,但是报错,各种异常,还有内存溢出,望教一个搭环境的方法,能运行的,谢谢兄弟了,
    qq 1365276552 邮箱 zhouxingfako@sina.com

发表评论

电子邮件地址不会被公开。 必填项已用*标注