ExtJS中可编辑表格的自定义风格的数据显示

在ExtJS实际开发中,我们经常碰到需要使用可编辑表格的地方。表格一般用于显示数据之用,但是在实际产品中,我们也会需要利用可编辑表格来完成数据处理和提交的工作。

在ExtJS 4.X系列中,Grid提供的可编辑功能可以很好的帮助我们实现这一需求。偶尔我们还需要对于编辑后的进行一些显示上的处理才能满足功能的需求:

  • 输入编辑的数据,根据输入的数据现实不同的显示效果。比如数字框,需要根据用户输入的数值,将单元格的数字效果转换为红色或者其它颜色,而表格保存时仅保留数值。
  • 输入编辑可选择下拉框数据,然后单元格内现实的下拉框的displayfield的内容,而提交的时候需要提交下拉框的valueField的对应的值。

下面将简单介绍利用Grid自带的单元格renderer API实现以上功能的过程。

对于第一个需求实现方法如下:

{
    xtype: 'gridcolumn',
    dataIndex: 'dataRange',
    text: '数据范围',
    editor: 'textfield',
    renderer: function(val) {
        if(val > 100) {
            return '<font color="red">'+val+"</font>";
        } else {
            return val;
        }
    }
}

对于第二个需求实现方法:

var comboboxStore = Ext.create('Ext.data.store', {
    data: [
        {'display': '是', 'value': '1'},
        {'display': '否', 'value': '2'}
    ],
    fields: [
        {name: 'display'},
        {name: 'value'}
    ]
});
........
//grid定义
{
    xtype: 'gridcolumn',
    dataIndex: 'dataRange',
    text: '数据范围',
    editor: Ext.create('Ext.form.field.ComboBox',{
        editable: false,
        queryMode: 'local',
        triggerAction: 'all',
        selectOnTab: true,
        store: comboboxStore,
        displayField: 'display',
        valueField: 'value',
        listClass: 'x-combo-list-small'
    }),
    renderer: function(val) {
        var index = comboboxStore.findExact('value',val);
        if(index != -1) {
            var rs = comboboxStore.getAt(index).data;
            return rs.display;
        }

    }
}

实现效果如图:

combobox

ExtJS作为一个完整的RIA开发框架,其内部的丰富组件已经提供了非常完整的API实现了,通常情况下,我们可以通过简单的API组合就可以实现满足功能所需要的实现方法。因此对于ExtJS的API文档和样例应该有一定的认识和理解。除了标准的组件支持,ExtJS还提供了一些其它的组合组件,例如:MutliSelect,itemselector等等。

itemselector

mutliselector

简化Javascript的异步编码

在开发项目中的前端功能时,经常会有这种场景:

用户双击图形界面某处或者点击某个按钮,弹出窗口显示一个显示具体信息的窗体或者消息窗。

通常情况下,这个窗体的内容包含由一个可显示的表单信息,可能包含下拉菜单,多选框等组件,这些内容都需要跟后端服务器进行交互获得最新的数据。

enter image description here

以往在开发时,我基本都是写类似以下的代码:

function A(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoA',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框A的store loadData
            storeA.loadData(resultData.data);
            B();
        }
    })
}
function B(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoB',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框B的store loadData
            storeB.loadData(resultData.data);

            C();

        }
    })
}

function C(){
    //示例使用Ext
    Ext.Ajax.request({
        url:'myAction.do?action=showInfoC',
        params:{
            id: 1
        },
        success:function(response){
            var resultData = Ext.decode(response.responseText);
            //下拉框C的store loadData
            storeC.loadData(resultData.data);
            render();   
        }
    })
}
function render(){
    configWin.show();
}

当然除了这种方法,我们也可以先渲染待显示的Window窗体,给每个下拉框绑定一个事件,在下拉框激活时再去后台获取数据。

就目前编写的方法本身来看,它讲这些本可以异步处理的工作串行化了,导致效率的降低。

另外还有一种场景就是使用模板的情况,并且可能还涉及到前端的国际化。那么在编写代码时,通常就会类似以下代码:

var render = function (template, data) {
  _.template(template, data);
};
$.get("template", function (template) {
  // something
  $.get("data", function (data) {
    // something
    $.get("l10n", function (l10n) {
      // something
      render(template, data, l10n);
    });
  });
});

在这种情况下,执行的过程仍然被串行化了,导致执行的效率下降,并且在层次较深的情况下,导致代码难以阅读和维护。

如果深度很深的情况就会出现,最后一页都是}的情况了

世界上本没有嵌套回调,写得人多了,也便有了}}}}}}}}}}}}

下面将简单介绍使用三个有用的JS库来帮助我们将这些代码逻辑转换真正异步并行,并且易于阅读和维护的代码。这三个库分别是Eventproxy,Step,Async。

一、使用Eventproxy

首先来看在实际项目使用的第一个场景:

enter image description here

如上图所示,深度嵌套的代码经过华丽变身后,转化为以上的漂亮代码了。当然上面的代码仍然存在问题,针对异常的处理并不完整。

EventProxy提供了多组API,可以满足不同场景的需求.

1.1 多类型异步协作

前面示例中的模板和国际化以及数据加载的过程的代码可以转换为以下代码:

var ep = new EventProxy();
ep.all('tpl', 'data', function (tpl, data) {
  // 在所有指定的事件触发后,将会被调用执行
  // 参数对应各自的事件名
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
  ep.emit('tpl', content);
});
db.get('some sql', function (err, result) {
  ep.emit('data', result);
});

all方法将handler注册到事件组合上。当注册的多个事件都触发后,将会调用handler执行,每个事件传递的数据,将会依照事件名顺序,传入handler作为参数。

1.2 重复异步协作

此处以读取目录下的所有文件为例,在异步操作中,我们需要在所有异步调用结束后,执行某些操作。

var ep = new EventProxy();
ep.after('got_file', files.length, function (list) {
  // 在所有文件的异步执行结束后将被执行
  // 所有文件的内容都存在list数组中
});
for (var i = 0; i < files.length; i++) {
  fs.readFile(files[i], 'utf-8', function (err, content) {
    // 触发结果事件
    ep.emit('got_file', content);
  });
}

after方法适合重复的操作,比如读取10个文件,调用5次数据库等。将handler注册到N次相同事件的触发上。达到指定的触发数,handler将会被调用执行,每次触发的数据,将会按触发顺序,存为数组作为参数传入。

在最近开发的流程设计器中,画布需要异步加载对应控件的类文件,在开发中采用了类似方法解决:

a、传入待渲染到画布中的JSON数据

b、解析JSON文件,异步加载每个需要使用到的控件对应的JS模块类文件

c、全部解析完成后,在当前画布中渲染图形效果

具体的代码也类似上面读取文件的过程实现。

enter image description here

 

1.3 持续型异步协作

此处以股票为例,数据和模板都是异步获取,但是数据会是刷新,视图会重新刷新。

var ep = new EventProxy();
ep.tail('tpl', 'data', function (tpl, data) {
  // 在所有指定的事件触发后,将会被调用执行
  // 参数对应各自的事件名的最新数据
});
fs.readFile('template.tpl', 'utf-8', function (err, content) {
  ep.emit('tpl', content);
});
setInterval(function () {
  db.get('some sql', function (err, result) {
    ep.emit('data', result);
  });
}, 2000);

 

tailall方法比较类似,都是注册到事件组合上。不同在于,指定事件都触发之后,如果事件依旧持续触发,将会在每次触发时调用handler,极像一条尾巴。

1.4 小结与注意事项 {#id-[实践]简化Javascript的异步编码-1.4小结与注意事项}

除了能够处理以上三种通常场景以外,Evenproxy还提供了Group,以及异常处理方法等,具体可以查看Eventproxy官方示例和文档。

  • 请勿使用all作为业务中的事件名。该事件名为保留事件。
  • 异常处理部分,请遵循Node的最佳实践。(如果在浏览器端运行则遵守浏览器端异常处理一般方法)

如果对Eventproxy的实现感兴趣也可以前往阅读其代码说明http://html5ify.com/eventproxy/eventproxy.html

 

二、使用Step

Step(https://github.com/creationix/step)提供的功能与Eventproxy类似,但是它针对的目标主要是Node的运行环境,所以这里将只会进行简单的介绍。

首先我们来看官方文档给出的一个实例:

Step(
  function readSelf() {
    fs.readFile(__filename, this);
  },
  function capitalize(err, text) {
    if (err) throw err;
    return text.toUpperCase();
  },
  function showIt(err, newText) {
    if (err) throw err;
    console.log(newText);
  }
);

 

在上面的示例中,我们将this
作为一个回调传给fs.readFile.当读取文件结束时,step会将读取结果作为参数传递给函数链中的下一个函数。紧接着在capitalize函数里可以完成一些同步任务并简单返回新的值,并且在我们调用回调时step会继续传递结果出去。

step还能提供类似eventproxy的重复的处理方法:

Step(
  function readDir() {
    fs.readdir(__dirname, this);
  },
  function readFiles(err, results) {
    if (err) throw err;
    // Create a new group
    var group = this.group();
    results.forEach(function (filename) {
      if (/\.js$/.test(filename)) {
        fs.readFile(__dirname + "/" + filename, 'utf8', group());
      }
    });
  },
  function showAll(err , files) {
    if (err) throw err;
    console.dir(files);
  }
);

对于Step提供的其它方法,可以前往其官方查看具体的示例说明。

 

三、使用Async

相比前面介绍的Eventproxy和Step来说,Async(https://github.com/creationix/step)提供的对于异步流程控制的处理方法更为丰富,它主要提供了两类API

  1. 提供集合相关的处理方法
  2. 提供异步流程控制的处理方法

3.1 Collections

3.2 Control Flow

我们这里将简单针对前文提及的几种场景进行API使用的介绍,其它的API使用方法可以前往官方网站查看。

前面提到的模板
国际化,数据最后渲染页面的方法使用async实现后的代码将如下,可以使用的API有两种series和parallel。

async.series([
    function(callback){
        fs.readFile('template.tpl', 'utf-8', function (err, content) {
          callback(null, coptent);
        });

    },
    function(callback){
        db.get('some sql', function (err, result) {
         callback(null, result);
        });

    }
],
// optional callback
function(err, results){
    console.log(results);
    //这种结果将以[]数组方式存储
});


async.series([
    tpl:function(callback){
        fs.readFile('template.tpl', 'utf-8', function (err, content) {
          callback(null, coptent);
        });

    },
    data:function(callback){
        db.get('some sql', function (err, result) {
         callback(null, result);
        });

    }
],
// optional callback
function(err, results){
    console.log(results);
    //这种结果将以{tpl:tpl,data:data}数组方式存储
});

series方法中的函数链将以定义的顺序执行。比如

async.series([
    function(callback){
        setTimeout(function(){
            console.log("start 1");
            callback(null, 1);
        }, 200);

    },
    function(callback){
        setTimeout(function(){
            console.log("start 2");
            callback(null, 2);
        }, 100);

    }
],
// optional callback
function(err, results){
    console.log(results);
});

以上示例代码使用settimeout来模拟代码执行的延时情况,以上结果将会打印:

start 1
start 2
[1, 2]

由于模板和数据之前并没有直接的依赖关系,那么我们可以改成并发异步的方法来执行。上面的代码只需要将series修改为parallel即修改并发异步执行的代码了。

前面的示例代码修改为并发执行的代码后,打印结果将会是:

start 2
start 1
[1,2]

可以看到两个函数的执行顺序将不再按照定义顺序执行,但是结果的顺序仍然保持顺序。

相比较Eventproxy和Step来说,Async提供了更多更丰富的API方法,足以满足各种场景下的需求。

比如执行的事件间是有依赖关系的,比如先根据一个序号查找该用户的信息,然后根据返回结果再执行下一个事件请求,数据需要在这些函数间进行传递.Step可以满足实现这一场景。

下面我们再来看Async提供给我们的解决方法:

async.waterfall([
    function(callback){
        console.log("start 1");
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        console.log("start 2");
        callback(null, 'three');
    },
    function(arg1, callback){
        console.log("start 3");
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    console.log(result);
   // result now equals 'done'    
});

上面的代码执行完成后的打印结果如下:

start 1
start 2
start 3
done

 

对于某些更复杂的场景,比如csser的一个应用场景:

  • 判断用户登录状态
  • 判断用户提交数据的合法性
  • 分析并获得可以存储的板贴数据
  • 查询并确认板贴所属贴板是否有效(是否存在,以及是否为发布者拥有)
  • 板贴入库
  • 更新关联操作(比如用户的板贴数、板贴列表,贴板的板贴数和板贴列表等等)
  • 产生用户动态
  • 产生标签动态
  • 通知关注者, 贴板成员,贴板作者
  • 最后返回结果

这一系列任务,如果组织不好,代码会相当凌乱,难以维护,借助
async.auto,产生了清晰明了的代码:

async.auto({
    // 分析并获取有效数据
    datas: function (callback) {},

    // 查贴板,确认是否存在
    board: function (callback) {},

    // 入库贴板内容
    post: ['datas', 'board', function (callback) {}],

    // 更新贴板表和用户表
    updateBoardAndUser: ['post', function (callback, result) {}],

    // 产生用户动态
    feed: ['post', function(callback, result) {}],

    // 产生标签动态
    tagFeed: ['feed', function (callback, result) {}],

    // 通知关注者, 贴板成员,贴板作者
    notify: ['post', function (callback, result) {}]

// 返回JSON结果
}, function (err, result) {});

 

总结

根据实际工作场景选择合适的库,可以帮助我们编写出易于阅读和维护的javascript异步代码,这可以让我们更容易的关注每个具体的事件背后的单一事件的实现。

ExtJS Grid 分组中groupTextTpl的参数使用

在我们使用ExtJS的表格组件(Grid)中经常会需要使用到分组的情况,像使用业务分组,然后组内显示该业务下挂的服务列表信息。通常情况下,分组数据的方式就有两种:服务端分组数据返回或者是客户端分组。在ExtJS的API文档中给出的示例也非常简单,其代码如下:

var grid = new Ext.grid.GridPanel({
    // A groupingStore is required for a GroupingView
    store: new Ext.data.GroupingStore({
        reader: reader,
        data: xg.dummyData,
        sortInfo:{field: 'company', direction: "ASC"},
        groupField:'industry'
    }),

    columns: [
        {id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'},
        {header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
        {header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
        {header: "Industry", width: 20, sortable: true, dataIndex: 'industry'},
        {header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
    ],

    view: new Ext.grid.GroupingView({
        forceFit:true,
        // custom grouping text template to display the number of items per group
        groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
    }),

    frame:true,
    width: 700,
    height: 450,
    collapsible: true,
    animCollapse: false,
    title: 'Grouping Example',
    iconCls: 'icon-grid',
    renderTo: document.body
});

这里需要指出的时,在大多数文档和示例代码中归于分组列头的显示都是使用本示例中的代码:

groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'

很少有代码提及关于groupTextTpl的其它相关内容。

在API文档中提及groupTextTpl的介绍中提到了以下几个可以使用的属性调用:

  • group 显示分组列名的名称,如果使用emptypGroupText属性,那么当原始Group的值为空时,将会显示emptyGroupText的值。
  • gvalue 显示分组列的原始值,如果原始值为空,显示也是空。不会调用emptyGroupText。
  • text 如果配置了header属性,则会使用header属性的值加上原始分组列内的值。如果原始值为空,会调用emptyGroupText的值。
  • groupId ExtJS对每一个分组分配的独立的编号,通常其组成如下:ext-{生成器编码如gen20,gen19等}-gp-{分组列名}-{分组列值}
  • startRow 每一个组在整个Grid当前分页中的起始行的索引,索引从0开始。
  • rs 每个组包含的结果集。
  • cls 分组头样式的class名称
  • style : 分组头样式

比如在一些场景下,有业务和服务之分,服务是从属于业务的,业务和服务均会提供相应的编号和名称,这种情况下会按照业务编号分组,而希望显示的分组头内容为业务名称,而组内排序需要使用服务编号来排序。在这种情况下,我们可以这么定义:

比如业务名称和编号的列定义为appId,appName,服务名称和编号的列定义为serviceId,serviceName。

var myDataStore = new Ext.data.GroupStroe({
    autoLoad: false,
    url:'loadata.do',
    sortInfo:{field:'serviceId',direction:'DESC'}, // 这个属性必须配置,顺序可以为DESC或者ASC
    reader: reader,
    groupField:'appId'
});

//定义Grid,不显示业务名称、业务编号和服务编号
var mygrid = new Ext.grid.GridPanel  ({
    store: mydatastore,
    columns: [
        {header: '业务名称', width:45, dataindex: 'appName',hidden:true,groupable:false,dataIndex:"appName"},
        {header: '业务编号', width:55, dataindex: 'appId',hidden:true,groupable:false,dataIndex:"appId"},
        {header: '服务编号', width:70, dataindex: 'serviceId',hidden:true,groupable:false,dataIndex:"serviceId"},
        {header: '服务名称',  width:90, dataindex: 'serviceName'},
        {header: '其它内容',  width:40, dataindex: 'otherInfo'}
    ],
    stripeRows: true,
    autoSizeColumns: true,
    autoSizeGrid: true,
    title:'Your Grid Title',
    collapsible: false,
    animCollapse: false,
    height: 445,
    columnLines: true
});

下面如果我们需要在分组头显示业务名称的时候,我们就需要在GridPanel对象中定义view对象:

view: new Ext.grid.GroupingView({
    forceFit:true,
    groupTextTpl: '分组 : {text}|{[ values.rs[0].data["appName"] ]} ({[values.rs.length]} {[values.rs.length > 1 ? "Sevices" :        "Sevice"]})'
})

使用这个视图定义后,显示的分组头部文字效果就类似如下:

//比如数据形如:
//[
// {appId:'1',appName:'业务1',serviceId:'1001',serviceName:'服务1'},
// {appId:'1',appName:'业务1',serviceId:'1002',serviceName:'服务2'},
// {appId:'2',appName:'业务2',serviceId:'2001',serviceName:'服务3'},
// {appId:'2',appName:'业务2',serviceId:'2002',serviceName:'服务4'},
// {appId:'3',appName:'业务3',serviceId:'3001',serviceName:'服务5'}
//]
// 显示在表格中的头部效果如下:
// 分组: 1 | 业务1 (2 Services)
// 分组: 2 | 业务2 (2 Services)
// 分组: 3 | 业务3 (1 Service)

撰写本文的目的是因为在实际的产品开发中遇到类似的需求,为了不修改了后台的排序代码,于是进行了以上的尝试。这说明在使用ExtJS时,其API文档可能有些内容并没有提到,但是要能从其文档中慢慢发现API的规律,并予以尝试,肯定会带来不少帮助的。

官方示例中只给出了values.rs.length的示例,那么从这里面我们就能发现values.rs肯定是这个组内的所有结果集了,这样要实现我们本身的需求目的就非常简单了。

让你的日志讲故事

这两天看到了不少基于timeline的应用,大家感兴趣的可以去参观facebook,github等网站上发布一些基于时间线功能的项目。

时间线像是一个故事轴,能比较好的展示一些立体信息。

于是自己想实现一些功能来为typecho甚至为其他平台服务,今天花了一上午时间完成一个预览版。

目前预览版的效果还是非常简单,有很多细节并没有完成。

先暂时放出一个效果图和预览页面。

有兴趣的可以随时去看预览页面。

页面效果会经常改变哟,因为我是直接在实际运行场景中运行。

正式版本预计会跟新版typecho一同发布,也有可能稍晚于新版本的发布。

目标:

1,实现全站时间线功能

2,实现自定义分类或者时间段时间线功能 这个实现了的话,就可以很好控制时间线范围这样更容易想成一个故事链。

3,归档时间线。这个效果会跟现在的效果差别非常大。还只是一个很初步的思路。

4,性能优化。

5,与主题自由集成。

目前正在修改的地方:

1,日期格式修正。

2,显示一部分摘要。

3,显示图片附件。

4,与主题集成。

5,插件化。

哥是写JAVA后台应用的。现在搞前台还真不容易。

php的?真心让我郁闷。

预览页面:神奇的时间线
预览图片:页面预览图

JQuery Tabpanel Plugins

之前写过一篇关于使用JQuery的Transfer动画效果实现的一个简单页面动画的效果,并且将之使用到新近参与的项目中。但是后来发现自己编写的部分代码性能和美观度上都存在一定的缺陷。尤其是在看过Extjs的Tanpanel控件后,觉得tabpanel更适合实际需求。由于自己对Extjs的熟悉度不够,所以并未采用Extjs的方案。采用的是JQuery中的类似插件。

 

目前从Google中发现的两款插件都是国人开发的,且效果都很美观大方。

 

1,界面类似Extjs的jqTabpanel.作者本身没有给该作品命名 .
   作者原文介绍地址:Jquery仿Extjs Tabpanel
   界面效果图:jqtabpanel.gif

 插件下载:A,作者网站     B,本地下载(包含使用说明) C,UUShare

2, jerichotab

    作者网站 :jQuery插件之Tabpanel
   效果预览图:2323.png

    在线预览

    该作者也开发了一些其他的不错的插件,具体可以前往 作者Blog 
    我很欣赏作者开发的jSlider 效果.该插件效果可以前往:Jquery插件开发

文件下载:A,jquery.jerichotab.js B,本地下载 C,UUShare

JQuery transfer效果一则

目前有如下需求:
如下图所示,上面的区域为主体内容区域,下面四个则为控制按钮区域。通过点击不同按钮显示不同的内容。
1,主要内容区域中内容为div包含的不同的Iframe内容.
2,点击下面每个按钮时,会有个动画效果。内容出现时,能表现内容是从该触发按钮过渡到主体内容区域。内容最小化时,内容区域则通过动画最小化到按钮区域.
3,选中的按钮要有高亮显示.

具体如下所示:

showcase.PNG

这样的效果类似目前很流行的相册效果和Tab效果.但是有存在区别:
a,这里的主题内容不需要滚动,且内容为iframe内容。
b,要有过渡的动画效果.

自己花了一点时间编写出一个基本的效果案例,目前在不同的浏览器下面的效果是略有区别的,并且有可能在IE6的效果存在问题。本人将后续进行修改和完善.
演示效果参考:演示效果预览

Struts2学习进阶(基础篇七)——本地化输出

说明:本文翻译自:struts-2.0.8docsdocsvalidating-input.html
     在验证输入这一小节中,我们使用验证框架来验证提交到表单的数据。在“本地化输出”这一节中,我们将验证信息移到消息资源文件中。

    当创建web应用程序的时候,我们发现我们在多个页面中常常使用一些相同的消息或字段标签。如果我们的系统由不同语言的用户使用时,我们也想要本地化消息。

一.本地化验证信息和属性值
    让我们增加一个消息资源文件,并且在其中加入验证信息和字段标签。
1.代码
    本框架将消息资源与类联系起来。为了为Logon action增加一个消息资源,我们需要将消息资源文件命名为Logon.properties,并且在Logon Action后设置它的classpath。但是,大多数人发现为某一个类使用一个单独的资源文件时间和低效率的工作。很多人更喜欢为一整个类包增加一个消息资源文件。为了做到这样,我们可以简单的为某个包增加一个paacage.properties文件。例如。我们为tutorial包增加一个资源文件tutorial/package.properties。该文件内容如下:

    requiredstring = ${getText(fieldName)} is required.
      password = Password
      username = User Name

     我们还需要改变一下验证器和Logon页面。Logon-validation.xml修改的地方:
    <message>Username is required</message>
      <message key=”requiredstring”/>

      <message>Password is required</message>
      <message key=”requiredstring”/>

     Logon,jsp修改的地方如下:

   <s:textfield label=”User Name” name=”username”/>
     <s:textfield label=”%{getText(‘username’)}” name=”username”/>

      <s:password label=”Password” name=”password” />
      <s:password label=”%{getText(‘password’)}” name=”password” />

2. 代码是如何工作的?
    1)“key”属性告诉验证器为一个消息资源捆绑验证;
    2)在资源捆绑中,表达式:${getText(fieldName)}告诉框架在资源文件中查找字段名。这种方式下我们能够为所有的非空验证使用相同的默认信息;
    3)相似地,在文本域中,表达式:%{getText(‘password’)}告诉框架在资源文件中寻找“password”。

二.本地化其余的信息
     其余的页面元素也能够被本地化。例如,我们需要在资源文件中增加“Hello World”和“Missing page”这两个消息。
1. 代码tutorial/package.properties:
# …
HelloWorld.message = Struts is up and running …
Missing.message = This feature is under construction. Please try again in the next interation.
    这对HelloWorld是可运行的,因为Hello World已经存在于包中。但是它对于默认的Missing action是不可用的,除非你将我们的基类加入到tutorial包中。

    TutorialSupport.java的代码如下:

package tutorial;
import com.opensymphony.xwork2.ActionSupport;
public class TutorialSupport extends ActionSupport {}
    并且还需要更新一下struts.xml默认的通配符映射,如下:

<action name=”*” class=”tutorial.TutorialSupport”>
<result>/{1}.jsp</result>
</action>
    现在,我们可以更新HelloWorld.jsp和Missing.jsp来查找信息。

    Missing.jsp的代码如下:

This feature is under construction. Please try again in the next interation.
<s:text name=”Missing.message”/>

    在HelloWorld的情况下,我们从Action类设置消息。让我们更新一下class,使用资源文件来代替它。HelloWorld的代码如下:

package tutorial;
public class HelloWorld extends ExampleSupport {
    public static final String MESSAGE = “HelloWorld.message”;
    public String execute() throws Exception {
        setMessage(getText(MESSAGE));
        return SUCCESS;
    }

//  
}

2. 代码是如何工作的?
   1)对于Missing.jsp,我们使用text标签来查找资源文件中的消息;
   2) 在HelloWorld中,我们在Action类中使用getText()方法来获得消息;
   3)  在HelloWorld.jsp中,只是显示由Action类设置的消息,所以不要做任何改变。

三.需要记住的东西

    框架支持国际化。为了本地化应用,我们增加了资源文件,并且更新某些元素或标签来应用资源文件中的资源而不是静态的文本。

Struts2学习进阶(基础篇六)——验证输入值

说明:该文翻译自struts-2.0.8docsdocsvalidating-input.html。
    在编写Action这一章节中,我们通过几行java代码来验证username和password。当然,在大型的应用程序中,这将是个很耗时的工作,即使这几行java代码也可能变成维护的重大负担。
    令人高兴的是,本框架提供了一个验证框架,这个验证框架能够对输入数据进行后台验证。

一.代码

     验证能够通过XML文档来描述,或者使用注解(annotations)。XML文档的名称以Action的名字为开头,以“-validation”作为文件名后缀。当我们想要验证Logon Action的时候,我们文件名可以命名为Logon-validation.xml。Logon-validation.xml的代码如下:

<!DOCTYPE validators PUBLIC
“-//OpenSymphony Group//XWork Validator 1.0.2//EN”
http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd“>
<validators>
    <field name=”username”>
        <field-validator type=”requiredstring”>
            <message>Username is required</message>
        </field-validator>
    </field>
    <field name=”password”>
        <field-validator type=”requiredstring”>
            <message>Password is required</message>
        </field-validator>
    </field>
</validators>
     在Logon.java后我们创建一个Logon-validation.xml文件。并将上述粘贴到该文件中。

    页面第一次显示的时候,我们不想引发验证。我们需要在被告诉有输入不正确前有机会输入数据。通过验证的一种方式是找到一个ActionSupport基类提供的特定的“input”方法。为了这样做,我们需要编辑Welcome页面和Logon的映射。

     Welcome.jsp的修改代码如下:

    <li><a href=”<s:url action=”Logon”/>”>Sign On</a></li>
    <li><a href=”<s:url action=”Logon_input”/>”>Sign On</a></li>

    struts.xml的修改代码如下:

    <action name=”Logon” class=”tutorial.Logon”>
    <action name=”Logon_*” method=”{1}” class=”tutorial.Logon”>

二.代码是如何工作的?
    为了打开Logon表单,Welcome页面涉及了Logon_input。
    1. 框架将这个引用于Logon_*映射相匹配起来;
    2. “method={1}”属性被“method=input”替换;
    3. 框架调用了Logon Action类的input方法;
    4. 因为“input”在一个特殊的方法的列表中,所以验证框架将不会调用;
    5.  默认的input方法返回一个“input”作为返回结果;
    6. 框架展现“Logon.jsp”来作为回应结果,没有带有任何的验证信息。
    为了提交Logon表单,Login页面引用了Logon。
    1. 框架为目标Action——Logon提供验证;
    2. 找到Logon-validation.xml文件,框架为该类创建了一个验证对象,这个验证对象基于XML文件;
    3. 验证器对输入的数据产生作用;
    4. 如果验证失败,错误信息被添加到内部序列中;
    5. 当所有的验证器都已经执行后,如果框架发现有错误信息产生,它寻找“input”结果对应的页面,而不调用Action类;
    6. 如果通过验证,调用Action的方法,返回“success”对应的结果。

三.需要记住的东西
    框架提供了一个验证框架。一个输入域可以绑定一个验证器集合。如果验证失败,框架将会返回输入信息页,并显示错误信息。为了通过验证,一个特殊的“input”方法将被调用,来代替默认的“execute”方法。

本文转摘自blogjava,阿蜜果的blog

Struts2学习进阶(基础篇五)——选择结果

说明:本文翻译自struts自带的doc,路经为:struts-2.0.8-allstruts-2.0.8docsdocsselecting-results.html

    在编写Action这一章节中,我们创建了一个Logon类来测试输入。在本章中,我们将使这个测试的结果起作用。

一.             选择一个“Input”结果

     在一个Action执行一个请求后,一个提供用来作为这个请求回应的结果被选择。这个结果可以简单的定向到一个HTML页面、一个JSP页面、一个FreeMaker或一个Velocity模板、或者这个结果可能构造一个PDF或某些更加复杂的输出(例如JasperReports)。在一个action映射中可能有多个可选的结果。为了指明那一个被选择,Action类返回一个与正确结果相对应的名字。

二.             代码

struts.xml的代码如下所示:

<action name=”Logon” class=”tutorial.Logon”>
<result type=”redirect-action”>Menu</result>
<result name=”input”>/tutorial/Logon.jsp</result>
</action>
三.             代码是如何工作的?

   1. 如果你在表单中输入username和password属性,Logon Action将会返回“success”;

   2. 如果你其中一者或两者都没有输入值,Logon Action将返回“input”,框架将会使用Logon.jsp作为回应。

    在Hello World章节中,我们的结果使用了默认的类型:Dispatcher。Dispatcher定向到另外的web资源。其它种类的视图能够被用来指明不同的结果类型。

     Logon映射为success(默认的返回值)使用了一种不同的返回类型。“redirect-action”使用Action的名字作为参数,接着使客户端定向到一个新的action。

四.             使用一个Stub页

     当我们开发web应用程序的时候,我们常常需要进行forward引用——我们需要引用到一个我们还没有开始写的action。例如,在文章的第一部分中,下一步是跳转到“Menu”页面。如果我们成功登录,我们就没有地方去了,因为“Menu”尚不存在。

     解决这种问题的方法是创建一个Stub“Menu”页。

     Menu.jsp的代码如下:

<html>
<head><title>Missing Feature</title></head>
<body>
<p>
    This feature is under construction.
    Please try again in the next interation.
</p>
</body>
</html>
    这段代码是如何工作的呢?

    1. 当登录class返回“Menu”时,框架将会将其与通配符映射对应起来;

    2. 框架现在将会返回一个stub的“Menu.jsp”。

    说明:如果你没有使用通配符,另一种注入一个丢失页面的方法就是指定一个<action-default-ref>元素。

五.             包括一个缺失页面

     如果你页到页的建造一个应用程序,建立一个标准的缺失页面时对应的标准页面是非常必要的。Menu.jsp的代码如下:

  <%@ taglib prefix=”s” uri=”/struts-tags” %>
  <s:include value=”Missing.jsp” />
     这段代码是如何工作的呢?

    当需要展现Menu.jsp的时候,它将会包括标准的Missing.jsp的内容。

六.             需要记住的东西

     本框架提供多种返回类型。一个Action能够通过名字选择正确的返回结果,而不用知道到底是哪一种返回类型。

Struts2学习进阶(基础篇四)——编写Action

说明:本文翻译自struts-2.0.8-allstruts-2.0.8docsdocscoding-actions.html
  

在使用Struts2标签这一章节中,我们实现了登录表单,在编写Action章节中,我们解释登录表单,并且根据不同的情况返回不同的结果码。

如果你已经编码完成,你可以用如下链接打开登录的Action:

http://localhost:8080/tutorial/Logon.action
并且输入一个合适的用户名和密码。因为你没有给这个Action任何的行为,映射重新显示了默认的Logon.jsp页面。

让我们新增一个Action类来使得登录表单更加有意思。

一.      代码

像例子中所示的,我们能够检查用户名和密码的值。如果其中一者或两者都为空,返回输入页面,否则,返回SUCCESS对应的页面,Logon.java对应的代码如下:

package tutorial;
import com.opensymphony.xwork2.ActionSupport;
public class Logon extends ActionSupport {

    public String execute() throws Exception {

        if (isInvalid(getUsername())) return INPUT;
        if (isInvalid(getPassword())) return INPUT;
        return SUCCESS;
    }

    private boolean isInvalid(String value) {
        return (value == null || value.length() == 0);
    }

    private String username;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    private String password;
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

}

二。代码是如何工作的?

框架自动为我们的username和password属性完成组装功能。我们所需要做的就是核对是否其中有属性为空。

三.   需要记住的东西

在web应用程序中,Action做了重量级的工作。Action与数据库系统和业务规则引擎相互作用,以便我们能够把一些简单的HTML变成复杂的、动态的web体验。

在做了这些工作之后,Action返回了一个结果码来指明框架下一步应该做什么工作。通常,下一步是跳转到成功结果页面,但在某些情况下,我们需要跳转到错误结果页。还在某一些情况下,Action不用担心产生的回应,仅仅决定哪些逻辑结果需要在下一步呈现。