ExtJS中的树的使用和总结
一.TreePanel 配置参数:
root:Ext.tree.TreeNode //根节点
loader:Ext.tree.TreeLoader //数据加载器
checkModel:"single" //复选框只能单选,多选为:multiple,级联:cascade
trackMouseOver:false //是否只有叶子节点可选
rootVisible:false //false不显示根节点,默认为true
autoScroll:true //超过范围自动出现滚动条
animate:true //展开和收缩时的动画效果
enableDrag:true //树的节点可以拖动,注意不是Draggable
enableDD:true //不仅可以拖动,还可以通过Drag改变节点的层次结构(drap和drop)
enableDrop:true //仅仅drop
lines:true //节点间的虚线条
useArrows:true //侧三角,false则为"+-"
二.TreeNode的基本配置参数:
id:"id" //节点ID
text:"节点文本" //节点显示的文本
cls:"folder/file" //节点显示的样式:文件夹或文件
pid:"id" //父节点的ID
leaf:true //是否是叶子节点
expanded:fasle //是否展开,默认不展开
checked:false //true则在text前有个选中的复选框,false则text前有个未选中的复选框,默认没有任何框框
href:"http://www.123.com" //节点的链接地址
hrefTarget:"mainFrame" //打开节点链接地址默认为blank,可以设置为iframe名称id,则在iframe中打开
qtip:"提示" //提示信息,不过要有Ext.QuickTips.init();
singleClickExpand:true //用单击文本展开,默认为双击
三.树节点的数据分析:
1.节点数据分为父节点和子节点,父节点需要以文件夹形式显示。
2.子节点与父节点的关联通过pid实现
例如:
[
{
"id": "01",
"text": "企业科",
"cls": "folder",
"children": [
{
"id": "qyk1",
"text": "企业科一号用户",
"cls": "file",
"pid": "01",
"leaf": true,
"autoid": 0,
"expanded": false
},
{
"id": "qyk2",
"text": "企业库二号用户",
"cls": "file",
"pid": "01",
"leaf": true,
"autoid": 0,
"expanded": false
}
],
"leaf": false,
"autoid": 0,
"expanded": false
}
]
注意一些属性名称:
四.实例分析:
1.创建触发域:
var selectedAuditRow;//定义全局变量————获取点击列表某一行时通过复选框获得的行号。 /*1.创建触发域*/ var auditTrigger = new Ext.form.TriggerField({ emptyText:"请选择...", allowBlank:false, readOnly:true, onTriggerClick:function(e){ //单击触发按钮触发的事件 selectedAuditRow = auditSm.getSelected(); auditTreeWin.show(); auditRoot.reload(); //每次点击触发域时重新加载树 } });
2.创建Record:
/*2.创建Record*/ var auditRecord = new Ext.data.Record.create([ {name:"did",type:"string"}, {name:"dname",type:"string"}, {name:"sdid",type:"string"}, {name:"sdname",type:"string"}, {name:"title",type:"string"}, {name:"orderid",type:"int"}, {name:"auditUserId",type:"string"} ]);
3.创建Store:
/*3.创建Store*/ var auditStore = new Ext.data.Store({ url:"getAuditDetail.up?doType=getAuditDetail", reader: new Ext.data.JsonReader({ root:"data" },auditRecord) });
4.创建表格复选框和ColumnModel:
/*4.创建ColumnModel*/ var auditSm = new Ext.grid.CheckboxSelectionModel(); var auditCm = new Ext.grid.ColumnModel({ columns:[ auditSm, { header:"编号", dataIndex:"id", hidden:true },{ header:"部门编号", dataIndex:"did", hidden:true },{ header:"部门名称", dataIndex:"dname", width:170, renderer:function(v){ return dname; } },{ header:"科室编号", dataIndex:"sdid", hidden:true },{ header:"科室名称", dataIndex:"sdname", width:170, renderer:function(v){ return sdname; } },{ header:"流程名称", dataIndex:"title", editor:new Ext.form.TextField({ id:"title" }), width:190 },{ header:"流程顺序", dataIndex:"orderid", width:140 },{ id:"auditUserId", header:"审核用户ID", dataIndex:"auditUserId", editor:auditTrigger, width:195 } ] });
5.创建GridPanel:
/*5.创建GridPanel*/ var auditGrid = new Ext.grid.EditorGridPanel({ title:"审核流程明细", store:auditStore, cm:auditCm, sm:auditSm, border:true, bodyBorder:true, split: true, clicksToEdit:1, stripeRows: true,//斑马线 loadMask:{msg:"正在加载数据,请稍后......"}, height:250, tbar:[ { text:"保存流程", iconCls:"save", handler:saveAuditDetail },{ text:"增加流程", iconCls:"add", handler:function(){ if(sdid == 0) { Ext.Msg.alert("系统提示","请先选择科室再增加审核流程"); return; } var newAudit = new auditRecord({ //此处创建新的Record did:did,//此处的did,dname,sdid,sdname是全局变量,在其他地方赋值,此处不详述 dname:dname, sdid:sdid, sdname:sdname, title:'', orderid:order, auditUserid:'' }); auditGrid.stopEditing(); //关闭表格的编辑状态 auditStore.add(newAudit); auditGrid.startEditing(0, 0); //激活第一行第一列的编辑状态 } },{ text:"删除流程", iconCls:"remove", handler:removeAuditDetail } ] });
6.创建树:
/*6.创建树*/ //6.1 定义根节点 var auditRoot = new Ext.tree.AsyncTreeNode({ id:'tree-root', text:"部门", expanded:true, //根节点默认展开(注意和TreePanel中的rootVisible:true的联系) draggable:false //根节点不可拖动 }); //6.2 定义节点数据加载器 var auditLoader = new Ext.tree.TreeLoader({ dataUrl:'getKeshiUsersByGrade.up?doType=getKeshiUsersByGrade', //此处不是url,和Store的Proxy不同 baseParams :{pid:''}, baseAttrs:{uiProvider:Ext.tree.TreeCheckNodeUI} //必须有该项,不然树节点无法出现选择框 }); //6.3 定义数据加载前触发方法 auditLoader.on("beforeload",function(treeLoader,node){ auditRoot.setText(dname); //加载前重新设置树的根节点的名称 treeLoader.baseParams.pid = node.id; treeLoader.baseParams.did = did; //加载前给定pid和did参数 },this); //6.4 定义树形面板,用于显示数据 var auditTree = new top.Ext.tree.TreePanel({ /**注意top!!!*/ root:auditRoot, //根节点 loader:auditLoader, //数据加载器 checkModel:"single", //复选框只能单选,多选为:multiple,级联:cascade rootVisible:true, //显示根节点 autoScroll:true, animate:false, enableDD:false, //不允许子节点拖动 onlyLeafCheckable:true, /* trackMouseOver:false, useArrows:true, */ width:250, listeners:{"click":function(node){ if(!node.isLeaf()){ node.toggle(); //点击可展开可收缩 }else{ node.ui.toggleCheck(); } }} });
注意:
1.树的加载器里必须加上uiProvider,用于显示复选框
2.加载前传入参数,在后台通过request.getParameter("param")方式获取
后台取数:
7.创建存放树的窗口:
var auditTreeWin = new top.Ext.Window({ /**注意top!!!*/ title:"用户选择", layout:"fit", closeAction:"hide", modal:true, width:250, height:250, items:auditTree, buttons:[ { text:"确定", handler:function(e){ /*点击选择树节点并将值显示在触发域的方法!*/ var node = auditTree.getChecked(); //获取被选中的树节点 if(!node || node.length < 1){ top.Ext.Msg.alert("系统提示","请选择用户!"); return; } var name = new Array(); var value = new Array(); for (var i = 0; i < node.length; i++) { name.push(node[i].text); value.push(node[i].id); } var data = selectedAuditRow.data; //获取选择的行的Record的Data数据对象 /*data["auditUserId"]= name.join(); —— —— 此处用这种方式给触发域赋值出错!!!必须用下面的方式!!! */ auditTrigger.setValue(name.join()); auditTreeWin.hide(); } },{ text:"取消", handler:function(){ auditTreeWin.hide(); } } ] });
注意:此处的树窗口和树面板需要加上“top”,否则无法显示。
8.创建单击触发域显示树窗口并显示选中项被勾选的方法:
/* 单击触发域打开树窗口并显示勾选项 */ auditTrigger.onTriggerClick = clickTriggerFun; function clickTriggerFun(e){ auditTreeWin.show(); var parentNodes = auditRoot.childNodes; if(parentNodes.length > 0) { showCheckedNode(parentNodes); } else { loadTree();//节点为0,则先加载树 } } /* 加载科室用户并勾选对应项 */ function loadTree(){ auditLoader.on("load",function(treeLoader,node){ showCheckedNode(node.childNodes); }); } /* 勾选对应的选中项,并展开选中项的父目录 */ function showCheckedNode(parentNodes){ //先展开再收缩父节点,让子节点充分显示 for (var i = 0; i < parentNodes.length; i++) { parentNodes[i].expand(); parentNodes[i].collapse(); } //先循环清楚全部勾选项 for (var i = 0; i < parentNodes.length; i++) { var childNodes = parentNodes[i].childNodes; for (var j = 0; j < childNodes.length; j++) { childNodes[j].ui.toggleCheck(false); } } //将触发域中的内容字符串分割为一个数组 var tgValue = auditTrigger.getValue(); childNodes = auditRoot.childNodes; var checkedArr = null; if(tgValue != null) { checkedArr = tgValue.split(","); } //循环对比,相对应的进行勾选 if(checkedArr != null && checkedArr.length > 0) { for (var i = 0; i < parentNodes.length; i++) { var childNodes = parentNodes[i].childNodes; for (var j = 0; j < childNodes.length; j++) { var text = childNodes[j].text; for (var k = 0; k < text.length; k++) { if(checkedArr[k] == text) { childNodes[j].ui.toggleCheck(true);//勾选 childNodes[j].parentNode.expand();//展开父节点 } } } } } }
9.创建保存科室审核流程明细的方法,只保存修改过的信息:
/*创建保存科室审核流程明细的方法,只保存修改过的信息*/ function saveAuditDetail(){ var rcds = auditStore.getModifiedRecords(); //获取修改过的记录集Records if(rcds&&rcds.length>0){ Ext.Msg.wait("正在保存..."); var rows=new Array(); for(var i=0;i<rcds.length;i++){ var rs = rcds[i]; //获取指定下标的某一行的记录Record var row=new Object(); var fields=rs.data; //获取该行记录的数据Data row = {did:fields.did,sdid:fields.sdid,title:fields["title"],orderid:fields["orderid"],auditUserId:fields["auditUserId"]}; rows.push(row); //注意此处获取属性方式:obj.id或者obj["id"]皆可 } //因为此处obj是data,是一个对象,这些id,name都是data的属性 //如果直接用record,则可以用它在Extjs中定义的get方法:rs.get("id") Ext.Ajax.request({ url: 'updateAuditDetail.up?doType=updateAuditDetail', method:'POST', timeout:30000, success: result, failure: function(){Ext.Msg.alert('失败','未成功提交数据!'); }, params:{updateSets :Ext.encode(rows)} //将数组转化为JSON字符串 }); function result(response, options){ var result = Ext.util.JSON.decode(response.responseText); if(result.success){ Ext.Msg.hide(); Ext.Msg.show({ title:'成功', msg: '数据保存成功', buttons: Ext.Msg.OK, icon: Ext.MessageBox.INFO }); //保存成功后刷新修改过的脏数据。 auditStore.rejectChanges(); auditStore.reload(); }else{ Ext.Msg.hide(); Ext.Msg.alert('失败','保存数据未成功!'); } } } }
注意:用Ext.encode(arr)方法可将数组转化为JSON字符串。
10.创建删除科室审核流程明细的方法,删除复选框勾选的记录:
/*创建删除科室审核流程明细的方法,删除复选框勾选的记录*/ function removeAuditDetail(){ var rcs = auditGrid.getSelectionModel().getSelections(); if(!rcs||rcs.length<1){ Ext.Msg.alert("提示","请先选择要删除的行"); return; } else{ Ext.Msg.confirm("确认删除","请确认是否删除选中的科室审核流程明细?",function(btn){ if(btn == "yes"){//选中"是"的按钮 Ext.Msg.wait("正在删除..."); var ids = new Array(); for (var i = 0; i < rcs.length; i++) { ids.push(rcs[i].get("id")); } //异步发请求 Ext.Ajax.request({ url:"deleteAuditDetails.up?doType=deleteAuditDetails", method:"POST", params:{auditIds:ids.join(",")}, success:function(response,option){ var result = Ext.decode(response.responseText); if (result.success) { Ext.Msg.alert("成功","选中的科室审核流程明细已成功删除!"); auditStore.rejectChanges(); auditStore.reload(); } }, failure:function(response,option){ Ext.Msg.alert("失败","删除过程中发生错误!"); } }); } }); } }
注意:用Ext.decode(jsonStr)方法可将JSON字符串转化为对象或数组。
11.查询、保存(更新)、删除的Servlet:
if ("getAuditDetail".equals(action)) { //查询
String sdid = request.getParameter("sdid");
UserPostDao userPostDao = new UserPostDao();
List auditDetails = userPostDao.getKeshiAuditDetailsBySdid(sdid);
String data = JSON.toJSONString(auditDetails); //集合转Json字符串
json = "{\"data\":" + data + "}";
response.setContentType("text/json;charset=UTF-8");
out = response.getWriter();
out.print(json);
out.close();
return;
}else if("updateAuditDetail".equals(action)){ //保存(更新)
UserPostDao userPostDao = new UserPostDao();
String sData = request.getParameter("updateSets");
List<KeshiAuditDetail> auditDetails = JSON.parseArray(sData, KeshiAuditDetail.class); //字符串转集合
boolean done = userPostDao.updateKeshiAuditDetails(auditDetails);
String str=null;
if(done){
str="{success:true}";
}else{
str="{success:false}";
}
response.setContentType("text/html;charset=UTF-8");
out=response.getWriter();
out.print(str);
out.close();
return;
}else if("deleteAuditDetails".equals(action)){ //删除
String ids = request.getParameter("auditIds");
UserPostDao userPostDao = new UserPostDao();
boolean done = userPostDao.deleteAllKeshiAuditDetails(ids);
String str =null;
if(done){
str="{success:true}";
}else{
str="{success:false}";
}
response.setContentType("text/html;charset=UTF-8");
out=response.getWriter();
out.print(str);
out.close();
return;
}
注意:
1.用FastJson的toJSONString(obj/list);方法可以将对象或集合转化为相应的Json字符串。
Dao方法获取数据的方式不同将导致转化的数据不同:
(1).普通集合:——错误方式
List list = query.list();
//获取到的是一堆Object数组的集合,Object数组没有属性——即没有键值对,所以转化的json字符串为非键值对格式的错误Json格式的字符串
转化的Json数据为:
[{0,"01",0,0,1,"企业科",0},{0,"qyk1",0,1,2,"企业科一号用户","01",0}]
(2).指定对象的集合:——正确方式
List<KeshiAuditDetail> auditDetails = query.addEntity(KeshiAuditDetail.class).list();
转化的Json数据为:
[
{
"autoid": 0,
"bm": "01",
"expand": 0,
"isLeaf": 0,
"level": 1,
"mc": "企业科",
"selected": 0
},
{
"autoid": 0,
"bm": "qyk1",
"expand": 0,
"isLeaf": 1,
"level": 2,
"mc": "企业科一号用户",
"pid": "01",
"selected": 0
}
]
2.用FastJson的JSON.parse(jsonStr);方法可将Json字符串转化为对象。
3.用FastJson的JSON.parseArray(jsonStr, T.class);方法可将Json字符串转化为指定类的集合。
五.树节点数据的获取和解析:
1.用于从数据库获取分级数据的实体类:
/**
* 用于从数据库获取分级数据,
* 并作为等待解析的树节点数据的实体类
*/
public class SimpleBean {
private String bm; //id
private String mc; //text
private String pid; //关联的父节点ID
private String href;
private int level; //节点所在层次;
private int isLeaf; //节点是否是叶子节点;
private int selected; //节点是否被选择;
private String target; //链接打开的位置
private int expand; //描述树节点时,表示是否展开
private long autoid;
......
...
..
}
2.获取并解析树节点数据的Servlet:
if("getKeshiUsersByGrade".equals(action)){//按级别
String jsonStr="";
TreeDao tdao=new TreeDao();
UserPostDao userPostDao = new UserPostDao();
String did = request.getParameter("did")==null?"0":request.getParameter("did");
//分级加载参数pid和loadAll的设置:
String pid=request.getParameter("pid")==null?"":request.getParameter("pid");
if(pid!=null&&(pid.startsWith("tree-root")||pid.startsWith("p-tree-root"))){
pid="";//首次加载时,从根目录加载,pid为根节点AsyncTreeNode的id!
}
String sloadAll=request.getParameter("loadAll");
int loadAll = 1;//loadAll:1:一次性全部加载,0:分级加载(点击树目录时加载)
if (sloadAll != null && !sloadAll.equals("")) {
loadAll=Integer.parseInt(sloadAll);
}
//获取数据并转化为树的格式的json数据:
if ("".equals(pid) ) {
List items = userPostDao.getKeshiUsersByGrade(did, pid, loadAll);
//JSON.toJSONString(items); 转化后的是普通的Json格式数据,而非树所规定的Json格式
jsonStr=tdao.getTreeNodesOfJson(items,loadAll,pid);
}
response.setContentType("text/json;charset=UTF-8");
out=response.getWriter();
out.print(jsonStr);
out.close();
return;
}
3.Dao——获取分级数据:
/**
* 获取科室用户制度树,按级别分类
* @param pid
* @param loadAll
* @return
*/
public List getKeshiUsersByGrade(String did,String pid,int loadAll){
List nodes=null;
Session session = null;
try{
session =HibernateUtil.getSession();
session.beginTransaction();
StringBuffer treeSql = new StringBuffer("select * from(select bm,mc,pid,isleaf,level from(");
treeSql.append(" select sdid as bm,sdname as mc, null as pid, 0 as isleaf from sub_department where pdid = ");
treeSql.append(did);
treeSql.append(" union select u.userid as bm, u.name as mc, k.sdid as pid, 1 as isleaf from users u join user_keshi k on u.userid = k.userid");
treeSql.append(" )connect by prior bm=pid start with pid is null )");
if(loadAll==0){//分级加载
if (pid == null||"".equals(pid))
treeSql.append(" WHERE PID IS NULL ");
else
treeSql.append(" WHERE PID='").append(pid).append("'");
}
nodes = session.createSQLQuery(treeSql.toString())
.addScalar("bm")
.addScalar("pid")
.addScalar("mc")
.addScalar("isLeaf",Hibernate.INTEGER)
.addScalar("level",Hibernate.INTEGER)
.setResultTransformer(Transformers.aliasToBean(SimpleBean.class)).list();
//此处直接用query.addEntity(SimpleBean.class);应该亦可,未验证
session.getTransaction().commit();
}catch(Throwable e){
log.error(e.toString());
HibernateUtil.endSession(session);
}finally{
HibernateUtil.endSession(session);
}
return nodes;
}
注意:
获取分级数据时,要对获取到的数据的值的格式进行规范。
4.解析树节点数据的工具类:
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import com.huaxia.entity.SimpleBean;
/**
* 创建时间: 2014年12月26日 上午11:39:36
* 类描述:
*/
public class ParseTreeUtil {
/**
* 树节点的解析
* @param items //需要解析的数据集合
* @param loadAll //分级加载/一次性加载
* @param pid //关联的父节点ID
* @return
* @throws Exception
*/
public String getTreeNodesOfJson(List items,int loadAll,String pid)throws Exception {
if(items==null)return "";
JSONArray jItems=new JSONArray();
try{
if(loadAll==1){//一次载入,从顶级编码开始,递归的加入子节点
for(int i=0;i<items.size();i++){
SimpleBean oi=(SimpleBean)items.get(i);
if(oi.getPid()==null||"".equals(oi.getPid())){
jItems.put(parseOptionItem(items,oi,null,0));
}
}
}else{
for(int i=0;i<items.size();i++){
SimpleBean oi=(SimpleBean)items.get(i);
JSONObject ji=new JSONObject();
ji.put("id", oi.getBm());
ji.put("pid", oi.getPid());
ji.put("text", oi.getMc());
ji.put("leaf", oi.getIsLeaf()==1);
ji.put("cls",oi.getIsLeaf()==1?"file":"folder");
ji.put("expanded", oi.getExpand()==1);
ji.put("autoid", oi.getAutoid());
jItems.put(ji);
}
}
}catch(Exception e){
throw new Exception("编码解析为JSON格式时发生错误:"+e.toString());
}
return jItems.toString();
}
private JSONObject parseOptionItem(List initItems,SimpleBean oi,Map mcids,int checkById)throws Exception{
if(oi==null){
return null;
}
JSONObject ji=new JSONObject();
ji.put("id", oi.getBm());
ji.put("pid", oi.getPid());
ji.put("text", oi.getMc());
ji.put("leaf", oi.getIsLeaf()==1);
ji.put("cls",oi.getIsLeaf()==1?"file":"folder");
ji.put("expanded", oi.getExpand()==1);
ji.put("autoid", oi.getAutoid());
if(mcids!=null){
if(checkById==1){
ji.put("checked", mcids.containsKey(String.valueOf(oi.getBm())));
}else{
ji.put("checked", mcids.containsKey(String.valueOf(oi.getAutoid())));
}
}
//检查当前节点的子节点
String nextPid=oi.getBm();
JSONArray cArray=new JSONArray();
for(int i=0;i<initItems.size();i++){
SimpleBean item=(SimpleBean)initItems.get(i);
if(item!=null&&nextPid.equals(item.getPid())){//找到子节点,分别递归形成子节点分支的集合。
cArray.put(parseOptionItem(initItems,item,mcids,checkById));
}
}
if(cArray.length()>0){
ji.put("children", cArray);
}
return ji;
}
}
Dao方法获取到的List转化为普通的JSON数据如下:
[
{
"autoid": 0,
"bm": "01",
"expand": 0,
"isLeaf": 0,
"level": 1,
"mc": "企业科",
"selected": 0
},
{
"autoid": 0,
"bm": "qyk1",
"expand": 0,
"isLeaf": 1,
"level": 2,
"mc": "企业科一号用户",
"pid": "01",
"selected": 0
},
{
"autoid": 0,
"bm": "qyk2",
"expand": 0,
"isLeaf": 1,
"level": 2,
"mc": "企业库二号用户",
"pid": "01",
"selected": 0
},
{
"autoid": 0,
"bm": "02",
"expand": 0,
"isLeaf": 0,
"level": 1,
"mc": "行财科",
"selected": 0
},
{
"autoid": 0,
"bm": "xck1",
"expand": 0,
"isLeaf": 1,
"level": 2,
"mc": "行财科一号用户",
"pid": "02",
"selected": 0
},
{
"autoid": 0,
"bm": "xck2",
"expand": 0,
"isLeaf": 1,
"level": 2,
"mc": "行财科二号用户",
"pid": "02",
"selected": 0
}
]
解析成树节点格式的JSON数据如下:
[
{
"id": "01",
"text": "企业科",
"cls": "folder",
"children": [
{
"id": "qyk1",
"text": "企业科一号用户",
"cls": "file",
"pid": "01",
"leaf": true,
"autoid": 0,
"expanded": false
},
{
"id": "qyk2",
"text": "企业库二号用户",
"cls": "file",
"pid": "01",
"leaf": true,
"autoid": 0,
"expanded": false
}
],
"leaf": false,
"autoid": 0,
"expanded": false
}
]
{
"id": "02",
"text": "行财科",
"cls": "folder",
"children": [
{
"id": "xck1",
"text": "行财科一号用户",
"cls": "file",
"pid": "02",
"leaf": true,
"autoid": 0,
"expanded": false
},
{
"id": "xck2",
"text": "行财科二号用户",
"cls": "file",
"pid": "02",
"leaf": true,
"autoid": 0,
"expanded": false
}
],
"leaf": false,
"autoid": 0,
"expanded": false
}
]
六.获取树节点分级数据的SQL语句的分析:

2.科室表数据:

3.用户科室关联表数据:

4.按照树节点格式查询科室数据:
select sdid as bm, sdname as mc, null as pid, 0 as isleaf
from sub_department
where pdid = 1;

5.查询与科室关联的用户数据:
select u.userid as bm, u.name as mc, k.sdid as pid, 1 as isleaf
from users u
join user_keshi k
on u.userid = k.userid;

6.查询科室用户分级数据:
select *
from (select bm, mc, pid, isleaf, level
from (select sdid as bm, sdname as mc, null as pid, 0 as isleaf
from sub_department
where pdid = 1
union
select u.userid as bm,
u.name as mc,
k.sdid as pid,
1 as isleaf
from users u
join user_keshi k
on u.userid = k.userid)
connect by prior bm = pid
start with pid is null);


