ofbiz学习——深入分析lookup标签实现原理
2017-10-17 09:58
477 查看
上一章介绍了一个查询界面的实现。但是查询条件里有个生产标识的控件lookup实现逻辑没有深入的分析,本章主要就是分析lookup的具体实现逻辑。
lookup标签生成的控件有2个功能:
1. 类似jquery autocomplete的功能,输入字符后会自动弹出提示选项供用户选择。
2. 右边有个小图标,点击小图标可以弹出一个查询窗口,通过查询条件筛选出查询结果后,选中目标选项,自动回填之前的输入框。
具体重点应该是lookup标签的配置。访问controller.xml文件,找到LookupProduct的配置:
<view-map name="LookupProduct" page="component://product/widget/catalog/LookupScreens.xml#LookupProduct" type="screen"/>
打开文件component://product/widget/catalog/LookupScreens.xml#LookupProduct
打开文件component://common/widget/CommonScreens.xml#LookupDecorator
打开文件component://common/template/includes/AjaxAutocompleteOptions.ftl
对应的后台请求:
从请求中可以看到,当输入三个字符“wit”后会马上发出一个post请求,其中表单数据只有一个term参数,其值就是我们输入的wit。url参数有3个:
ajaxLookup = Y 。 表示这个请求是个ajax请求,LookupDecorator中有个判断,如果该参数等于Y,则表示是autocomplete功能,否则就是弹出查询窗口功能。
_LAST_VIEW_NAME_ =FindProductionRun
这里可以不用关注这个参数
searchValueFieldName:
productId productId就是前面field定义的="productId"
很容易的可以知道entityName的值就是在component://product/widget/catalog/LookupScreens.xml#LookupProduct文件中定义的:
entityConditionList主要是下面这条代码:
displayFieldsSet和displayFields好像数据基本都是一样的。
displayFieldsSet对应的sql语句是:
displayFields对应的sql语句是:
findOptions这个暂时不管吧。
所以lookup标签能实现autocomplete功能,主要是使用的公共的CommonScreens.xml#LookupDecorator装饰器。而具体到例子中的“产品标识”的应用,则主要是配置了component://product/widget/catalog/LookupScreens.xml#LookupProduct
其中最关键的是要配置如下2个参数:
如要实现autocomplete功能,需要配置好entityName和searchFields参数。
如要实现弹出查询窗口功能,则需要实现查询窗口,并在对应连接添加点击触发set_value函数。
在分析调试过程中发现个现象,就是但lookup对应的输入框失去焦点时,会触发一个post请求,请求信息如下:
我们可以注意到多了个参数searchType=EQUALS
重点注意:
1. 在文件component://product/widget/catalog/LookupScreens.xml#LookupProduct中下面这个要配置才能再弹出窗口实现国际化。
<property-map resource="BookingUiLabels" map-name="uiLabelMap" global="true"/>
2. 配置searchFields时,里面的数组必须是逗号+空格分割,之前我只用了一个逗号分割总是报错,后来逗号后面加个空格就好了。
<set field="searchFields" value="[courseId, courseName]"/>
lookup标签生成的控件有2个功能:
1. 类似jquery autocomplete的功能,输入字符后会自动弹出提示选项供用户选择。
2. 右边有个小图标,点击小图标可以弹出一个查询窗口,通过查询条件筛选出查询结果后,选中目标选项,自动回填之前的输入框。
1. 先找出相关的源码
<field name="productId" title="${uiLabelMap.ProductProductId}"><lookup target-form-name="LookupProduct"/></field>
具体重点应该是lookup标签的配置。访问controller.xml文件,找到LookupProduct的配置:
<view-map name="LookupProduct" page="component://product/widget/catalog/LookupScreens.xml#LookupProduct" type="screen"/>
打开文件component://product/widget/catalog/LookupScreens.xml#LookupProduct
<screen name="LookupProduct"> <section> <condition> <if-service-permission service-name="catalogPermissionCheck" main-action="VIEW"/> </condition> <actions> <property-map resource="ProductUiLabels" map-name="uiLabelMap" global="true"/> <set field="title" value="${uiLabelMap.PageTitleLookupProduct}"/> <set field="queryString" from-field="result.queryString"/> <set field="entityName" value="Product"/> <set field="searchFields" value="[productId, internalName, brandName]"/> </actions> <widgets> <decorator-screen name="LookupDecorator" location="component://common/widget/CommonScreens.xml"> <decorator-section name="search-options"> <include-form name="LookupProduct" location="component://product/widget/catalog/FieldLookupForms.xml"/> </decorator-section> <decorator-section name="search-results"> <include-form name="ListLookupProduct" location="component://product/widget/catalog/FieldLookupForms.xml"/> </decorator-section> </decorator-screen> </widgets> </section> </screen>
打开文件component://common/widget/CommonScreens.xml#LookupDecorator
<screen name="LookupDecorator"> <section> <condition> <not><if-compare operator="equals" value="Y" field="parameters.ajaxLookup"/></not> </condition> <widgets> <section> <actions> <property-map resource="CommonUiLabels" map-name="uiLabelMap" global="true"/> <service service-name="getUserPreferenceGroup" result-map="prefResult"> <field-map field-name="userPrefGroupTypeId" value="GLOBAL_PREFERENCES"/> </service> <set field="userPreferences" from-field="prefResult.userPrefMap" global="true"/> <property-map resource="general" map-name="generalProperties" global="true"/> <set field="visualThemeId" from-field="userPreferences.VISUAL_THEME" global="true"/> <set field="defaultOrganizationPartyId" from-field="userPreferences.ORGANIZATION_PARTY" global="true"/> <service service-name="getVisualThemeResources"> <field-map field-name="visualThemeId"/> <field-map field-name="themeResources" from-field="layoutSettings"/> </service> <set field="layoutSettings" from-field="themeResources" default-value="${layoutSettings}" global="true"/> <set field="messagesTemplateLocation" from-field="layoutSettings.VT_MSG_TMPLT_LOC[0]" default-value="component://common/template/includes/Messages.ftl"/> </actions> <widgets> <section> <condition> <if-compare value="layer" operator="not-equals" field="parameters.presentation"/> </condition> <widgets> <platform-specific><html><html-template location="component://common/template/includes/Lookup.ftl" /></html></platform-specific> </widgets> </section> <platform-specific><html><html-template location="${messagesTemplateLocation}"/></html></platform-specific> <section> <condition> <not><if-empty-section section-name="body"/></not> </condition> <widgets> <decorator-section-include name="body"/> </widgets> <fail-widgets> <screenlet title="${title}" id="findScreenlet" collapsible="true" padded="false"> <container id="search-options"> <decorator-section-include name="search-options"/> </container> </screenlet> <screenlet> <container id="search-results"> <decorator-section-include name="search-results"/> </container> </screenlet> </fail-widgets> </section> <section> <condition> <if-compare value="layer" operator="not-equals" field="parameters.presentation"/> </condition> <widgets> <platform-specific><html><html-template location="component://common/template/includes/LookupFooter.ftl"/></html></platform-specific> </widgets> </section> </widgets> </section> </widgets> <fail-widgets> <section> <actions> <property-map resource="CommonUiLabels" map-name="uiLabelMap" global="true"/> <set field="searchType" from-field="parameters.searchType" default-value="${searchType}"/> <script location="component://common/groovyScripts/FindAutocompleteOptions.groovy"/> </actions> <widgets> <decorator-screen name="AjaxGlobalDecorator"> <decorator-section name="body"> <platform-specific> <html> <html-template location="component://common/template/includes/AjaxAutocompleteOptions.ftl" /> </html> </platform-specific> </decorator-section> </decorator-screen> </widgets> </section> </fail-widgets> </section> </screen>打开文件component://common/groovyScripts/FindAutocompleteOptions.groovy
import org.apache.ofbiz.base.util.StringUtil import org.apache.ofbiz.base.util.UtilDateTime import org.apache.ofbiz.base.util.Debug import org.apache.ofbiz.entity.util.EntityFindOptions import org.apache.ofbiz.entity.condition.EntityCondition import org.apache.ofbiz.entity.condition.EntityConditionList import org.apache.ofbiz.entity.condition.EntityExpr import org.apache.ofbiz.entity.condition.EntityFieldValue import org.apache.ofbiz.entity.condition.EntityFunction import org.apache.ofbiz.entity.condition.EntityOperator import org.apache.ofbiz.entity.util.EntityUtilProperties def mainAndConds = [] def orExprs = [] def entityName = context.entityName def searchFields = context.searchFields def displayFields = context.displayFields ?: searchFields def searchDistinct = Boolean.valueOf(context.searchDistinct ?: false) def searchValueFieldName = parameters.term def fieldValue = null if (searchValueFieldName) { fieldValue = searchValueFieldName } else if (parameters.searchValueFieldName) { // This is to find the description of a lookup value on initialization. fieldValue = parameters.get(parameters.searchValueFieldName) context.description = "true" } def searchType = context.searchType def displayFieldsSet = null def conditionDates = context.conditionDates def fromDateName = null def thruDateName = null def filterByDateValue = null //If conditionDates is present on context, resolve values use add condition date to the condition search if (conditionDates) { filterByDateValue = conditionDates.filterByDateValue ?: UtilDateTime.nowTimestamp() fromDateName = conditionDates.fromDateName ?: null thruDateName = conditionDates.thruDateName ?: null //if the field filterByDate is present, init default value for fromDate and thruDate if (!fromDateName && !thruDateName) { fromDateName = "fromDate" thruDateName = "thruDate" } } if (searchFields && fieldValue) { def searchFieldsList = StringUtil.toList(searchFields) displayFieldsSet = StringUtil.toSet(displayFields) if (context.description && fieldValue instanceof java.lang.String) { returnField = parameters.searchValueFieldName } else { returnField = searchFieldsList[0] //default to first element of searchFields displayFieldsSet.add(returnField) //add it to select fields, in case it is missing } context.returnField = returnField context.displayFieldsSet = displayFieldsSet if ("STARTS_WITH".equals(searchType)) { searchValue = fieldValue.toUpperCase() + "%" } else if ("EQUALS".equals(searchType)) { searchValue = fieldValue } else {//default is CONTAINS searchValue = "%" + fieldValue.toUpperCase() + "%" } searchFieldsList.each { fieldName -> if ("EQUALS".equals(searchType)) { orExprs.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(searchFieldsList[0]), EntityOperator.EQUALS, searchValue)) return //in case of EQUALS, we search only a match for the returned field } else { orExprs.add(EntityCondition.makeCondition(EntityFunction.UPPER(EntityFieldValue.makeFieldValue(fieldName)), EntityOperator.LIKE, searchValue)) } } } /* the following is part of an attempt to handle additional parameters that are passed in from other form fields at run-time, * but that is not supported by the Jquery Autocompleter, but this is still useful to pass parameters from the * lookup screen definition: */ def conditionFields = context.conditionFields if (conditionFields) { // these fields are for additonal conditions, this is a Map of name/value pairs for (conditionFieldEntry in conditionFields.entrySet()) { if (conditionFieldEntry.getValue() instanceof java.util.List) { def orCondFields = [] for (entry in conditionFieldEntry.getValue()) { orCondFields.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(conditionFieldEntry.getKey()), EntityOperator.EQUALS, entry)) } mainAndConds.add(EntityCondition.makeCondition(orCondFields, EntityOperator.OR)) } else { mainAndConds.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(conditionFieldEntry.getKey()), EntityOperator.EQUALS, conditionFieldEntry.getValue())) } } } if (orExprs && entityName && displayFieldsSet) { mainAndConds.add(EntityCondition.makeCondition(orExprs, EntityOperator.OR)) //if there is an extra condition, add it to main condition list if (context.andCondition && context.andCondition instanceof EntityCondition) { mainAndConds.add(context.andCondition) } if (conditionDates) { def condsDateList = [] if (thruDateName) { def condsByThruDate = [] condsByThruDate.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(thruDateName), EntityOperator.GREATER_THAN, filterByDateValue)) condsByThruDate.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(thruDateName), EntityOperator.EQUALS, null)) condsDateList.add(EntityCondition.makeCondition(condsByThruDate, EntityOperator.OR)) } if (fromDateName) { def condsByFromDate = [] condsByFromDate.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(fromDateName), EntityOperator.LESS_THAN_EQUAL_TO, filterByDateValue)) condsByFromDate.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(fromDateName), EntityOperator.EQUALS, null)) condsDateList.add(EntityCondition.makeCondition(condsByFromDate, EntityOperator.OR)) } mainAndConds.add(EntityCondition.makeCondition(condsDateList, EntityOperator.AND)) } def entityConditionList = EntityCondition.makeCondition(mainAndConds, EntityOperator.AND) String viewSizeStr = context.autocompleterViewSize if (viewSizeStr == null) { viewSizeStr = EntityUtilProperties.getPropertyValue("widget", "widget.autocompleter.defaultViewSize", delegator) } Integer autocompleterViewSize = Integer.valueOf(viewSizeStr ?: 10) EntityFindOptions findOptions = new EntityFindOptions() findOptions.setMaxRows(autocompleterViewSize) findOptions.setDistinct(searchDistinct) autocompleteOptions = delegator.findList(entityName, entityConditionList, displayFieldsSet, StringUtil.toList(displayFields), findOptions, false) if (autocompleteOptions) { context.autocompleteOptions = autocompleteOptions } }
打开文件component://common/template/includes/AjaxAutocompleteOptions.ftl
<#if description??> <#if autocompleteOptions??> <#list autocompleteOptions as autocompleteOption> <#assign displayString = ""/> <#list displayFieldsSet as key> <#assign field = autocompleteOption.get(key)!> <#if field?has_content> <#if (key != context.returnField)> <#assign displayString = displayString + field + " "> </#if> </#if> </#list> <#if (displayString?trim?has_content )>${displayString?trim}</#if> </#list> </#if> <#else> <script type="text/javascript"> var autocomp = [ <#if autocompleteOptions?has_content> <#if !displayReturnField??> <#assign displayReturnField = Static[ "org.apache.ofbiz.entity.util.EntityUtilProperties"].getPropertyValue( "widget", "widget.autocompleter.displayReturnField", delegator)> </#if> <#list autocompleteOptions as autocompleteOption> { <#assign displayString = ""/> <#assign returnField = ""/> <#list displayFieldsSet as key> <#assign field = autocompleteOption.get(key)!> <#if field?has_content> <#if (key == context.returnField)> <#assign returnField = field/> <#else> <#assign displayString = displayString + StringUtil.wrapString(field?string) + " "> </#if> </#if> </#list> <#if ("Y" == displayReturnField)> <#assign displayString = displayString + "[" + returnField + "]"> </#if> "id": "${returnField}", "label": "<#if (displayString?trim?has_content )>${displayString?trim}<#else>${returnField}</#if>", "value": "${returnField}" }<#if autocompleteOption_has_next>,</#if> </#list> <#else> { "id": "", "label": "${uiLabelMap.CommonNoRecordFound}", "value": "" } </#if>]; </script> </#if>
2. 分析autocomplete功能。
2.1 登录ofbiz后台系统 -》生产 -》 按F12调出开发者工具 -》 选择Network页签 -》 产品标识输入款输入字符串“wit”
然后我们能看到如下界面:对应的后台请求:
从请求中可以看到,当输入三个字符“wit”后会马上发出一个post请求,其中表单数据只有一个term参数,其值就是我们输入的wit。url参数有3个:
ajaxLookup = Y 。 表示这个请求是个ajax请求,LookupDecorator中有个判断,如果该参数等于Y,则表示是autocomplete功能,否则就是弹出查询窗口功能。
_LAST_VIEW_NAME_ =FindProductionRun
这里可以不用关注这个参数
searchValueFieldName:
productId productId就是前面field定义的="productId"
2.2 分析FindAutocompleteOptions.groovy
这个脚本有点长,先从后面往前面分析吧。先看下面代码:autocompleteOptions = delegator.findList(entityName, entityConditionList, displayFieldsSet, StringUtil.toList(displayFields), findOptions, false)从这里我们知道了输入的wit三个字符后,会通过这个语句到数据库里查找匹配的数据记录返回。
很容易的可以知道entityName的值就是在component://product/widget/catalog/LookupScreens.xml#LookupProduct文件中定义的:
<set field="entityName" value="Product"/>
entityConditionList主要是下面这条代码:
searchFieldsList.each { fieldName -> if ("EQUALS".equals(searchType)) { orExprs.add(EntityCondition.makeCondition(EntityFieldValue.makeFieldValue(searchFieldsList[0]), EntityOperator.EQUALS, searchValue)) return //in case of EQUALS, we search only a match for the returned field } else { orExprs.add(EntityCondition.makeCondition(EntityFunction.UPPER(EntityFieldValue.makeFieldValue(fieldName)), EntityOperator.LIKE, searchValue)) } }由于LookupScreens.xml#LookupProduct中配置了
<set field="searchFields" value="[productId, internalName, brandName]"/>所以entityConditionList最终等效的where语句是:
where (product_Id like '%wit%' orinternal_Name like '%wit%' or brand_Name like '%wit%' )
displayFieldsSet和displayFields好像数据基本都是一样的。
displayFieldsSet对应的sql语句是:
select product_Id, internal_Name, brand_Name
displayFields对应的sql语句是:
order by product_Id, internal_Name, brand_Name
findOptions这个暂时不管吧。
所以lookup标签能实现autocomplete功能,主要是使用的公共的CommonScreens.xml#LookupDecorator装饰器。而具体到例子中的“产品标识”的应用,则主要是配置了component://product/widget/catalog/LookupScreens.xml#LookupProduct
其中最关键的是要配置如下2个参数:
<set field="entityName" value="Product"/> <set field="searchFields" value="[productId, internalName, brandName]"/>
3. 弹出查询窗口功能
该功能和其他普通界面区别不到,主要是该功能是通过layer方式打开。主要要注意的地方是component://product/widget/catalog/FieldLookupForms.xml#ListLookupProduct中的如下定义:<field name="productId" title="${uiLabelMap.ProductProductId}" widget-style="buttontext"> <hyperlink description="${productId}" target="javascript:set_value('${productId}')" also-hidden="false" target-type="plain"/> </field>具体set_value函数的实现就不深入展开了,但正是因为有了该定义,所以才会实现点击弹出的查询窗口选项后,会自动把产品标识回填到原页面,并关闭弹出窗口。
4. 总结
lookup标签使用有了基本的了解。如要实现autocomplete功能,需要配置好entityName和searchFields参数。
如要实现弹出查询窗口功能,则需要实现查询窗口,并在对应连接添加点击触发set_value函数。
在分析调试过程中发现个现象,就是但lookup对应的输入框失去焦点时,会触发一个post请求,请求信息如下:
我们可以注意到多了个参数searchType=EQUALS
重点注意:
1. 在文件component://product/widget/catalog/LookupScreens.xml#LookupProduct中下面这个要配置才能再弹出窗口实现国际化。
<property-map resource="BookingUiLabels" map-name="uiLabelMap" global="true"/>
2. 配置searchFields时,里面的数组必须是逗号+空格分割,之前我只用了一个逗号分割总是报错,后来逗号后面加个空格就好了。
<set field="searchFields" value="[courseId, courseName]"/>
相关文章推荐
- 学习笔记一:深入分析Volatile的实现原理
- 深入Java集合学习系列:ArrayList的实现原理
- WPF学习笔记二 依赖属性实现原理及性能分析
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:HashMap的实现原理
- 深入Java集合学习系列:ArrayList的实现原理
- 深入Java集合学习系列:HashSet的实现原理
- 深入Java集合学习系列:HashMap的实现原理
- 深入Java集合学习系列:HashMap的实现原理
- 深入分析Volatile的实现原理
- 深入分析Volatile的实现原理
- 【JfaceTextFramework学习笔记之四】TextViewer实现原理简单分析
- 深入Java集合学习系列:LinkedHashMap的实现原理
- (精)Spring IOC核心源码学习III:bean标签和自定义标签实现原理
- 深入Java集合学习系列:LinkedHashSet的实现原理
- 深入Java集合学习系列:ArrayList的实现原理
- 深入Java集合学习系列:LinkedHashMap的实现原理
- 深入Java集合学习系列:HashSet的实现原理
- 深入Java集合学习系列:LinkedHashSet的实现原理
- 深入Java集合学习系列:ArrayList的实现原理