package org.jblooming.waf.html.input; import org.hibernate.HibernateException; import org.jblooming.ontology.IdentifiableSupport; import org.jblooming.ontology.Lookup; import org.jblooming.ontology.LookupInt; import org.jblooming.oql.OqlQuery; import org.jblooming.oql.QueryHelper; import org.jblooming.persistence.exceptions.PersistenceException; import org.jblooming.utilities.HashTable; import org.jblooming.utilities.JSP; import org.jblooming.utilities.ReflectionUtilities; import org.jblooming.waf.html.button.ButtonImg; import org.jblooming.waf.html.button.ButtonLink; import org.jblooming.waf.html.core.HtmlBootstrap; import org.jblooming.waf.html.core.JspHelper; import org.jblooming.waf.html.display.Img; import org.jblooming.waf.settings.I18n; import org.jblooming.waf.settings.PersistenceConfiguration; import org.jblooming.waf.view.PageSeed; import org.jblooming.waf.view.PageState; import javax.servlet.ServletException; import javax.servlet.jsp.PageContext; import java.io.IOException; import java.util.*; /** * The smart combo is a composite object. It is made by the hidden field (called "fieldName") were the selected ID is stored. * Usually you should refer this field. In some cases you'll like refer to the value of inserted input text (for instance if you want to * create filter businessLogic "on the fly") that is called "fieldName"+TEXT_FIELD_POSTFIX. The third component is the iframe, shared between * all the combo's in the page. * SmartCombo uses onBlur, onKeyUp, onFocus javascript event. If you need to add handle you must use the onBlurAdditionalScript property. * <p/> * toHtml saves in a map on session with key fieldName and value itself. * this in order to make iself instantiatible from the part of jsp that produces the list; * this is limited in the sense that if two pages in the same session simultaneously instantiate a combo * with the same field name, there might be leaked of filtered data among them. it may be improved on need. */ public class SmartCombo extends JspHelper implements HtmlBootstrap { public boolean shooted = false; public boolean useQbe; public boolean convertToUpper = false; public int heigth; public int minCharToQuery = 1; public String fieldName; public String fieldClass = "formElements"; public String separator = " "; public String label; public String innerLabel; // printed inside the field public String initialSelectedCode; public int tabIndex; public int maxLenght; public String script; public String tip; public String onBlurAdditionalScript; public boolean disabled; public boolean firstEmpty = false; //used in classic moodality only public ButtonImg linkToEntity; public int columnToCopyInDescription = 1; //public boolean showChoose = true; /** * The query must select at least two properties (in case you have one, just repeat it) limited to the select part. * Then there are two mandatory parameter "whereForFiltering" and "whereForId" to specify how to retrieve data * Both MUST uses the parameter named SmartCombo.FILTER_PARAM_NAME. Additional fixed parameter filter must be defined * in the query and set on "fixedParams" map * <p/> * Of the projected properties (columns for the not so priviledged): * 0 element is used as value for the hidden field (ID usually) * columnToCopyInDescription element is used for filterning and hence to fill the text field * from 1 onwards are used for display */ public String hql; public String whereForFiltering; public String whereForId; /** * this query is used when a master-slave smart combos are in action. This query is similar to "whereForFiltering" but must contains a #PARAM# in the string. * #PARAM# will be replaced by ajax when master changes */ public String whereForRefresh; /** * these usage is left as an exercise */ public QueryHelper queryHelperForFiltering; /** * additional fixed values for the hql query. Used to set fixed filter (eg.: "hidden" , true) */ public Map<String, Object> fixedParams = new HashTable(); /** * key control */ public String actionListened; public int keyToHandle; public String launchedJsOnActionListened; public static final String TEXT_FIELD_POSTFIX = "_txt"; public static final String LINKENTITY_POSTFIX = "_lnk"; public static final String INITIALIZE = "IN"; public static final String DRAW_INPUT = "DI"; public static final String FILTER_PARAM_NAME = "filter"; public int iframe_width = 300; public int iframe_height = 100; public int maxRowToFetch = 20; /** * teoros * if false nothing changes, search for filter+"%" * if true search for "%"+filter+"%" */ public boolean searchAll = false; /** * a valid value must be selected */ public boolean required = false; public boolean readOnly = false; public boolean classic = false; public boolean autoSubmit = false; public String idForm; /** * when true if found a matching row, select it and close the dropdown */ public boolean closeAutomatically = false; /** * if addAllowed = true no error for un-existing values. Should be used if new values are added in the controller */ public boolean addAllowed = false; /** * if true the resultset is rendered in table. Otherwise the properties will be separed by " ", and the joined string is returned */ public boolean useTableForResults = true; public boolean preserveOldValue = true; /** * this script if filled is launched when a value for the smart combo has been selected */ public String onValueSelectedScript; /** * this set of values are always added in top of others and respective objects are removed from the query part. * The format MUST be congruent with the query passed */ public List<Object[]> additionalLines = null; public Set<Object> highlightedIds = new HashSet(); public SmartCombo() { this.urlToInclude = "/commons/layout/smartCombo/partSmartCombo.jsp"; } public SmartCombo(String fieldName, String hql, String whereForFiltering, String whereForId) { this(); this.fieldName = fieldName; this.hql = hql; this.whereForFiltering = whereForFiltering; this.whereForId = whereForId; } public SmartCombo(String fieldName, Class<? extends Lookup> lookupClass) { this(fieldName, "select p.id, p.description from " + lookupClass.getName() + " as p", "where upper(p.description) like :" + SmartCombo.FILTER_PARAM_NAME + " order by " + (ReflectionUtilities.extendsOrImplements(lookupClass, LookupInt.class) ? "p.intValue" : "p.description"), "where p.id = :" + SmartCombo.FILTER_PARAM_NAME); convertToUpper = true; } public SmartCombo(String fieldName, Class<? extends IdentifiableSupport> identifiableClass , String propertyToList) { this(fieldName, "select p.id, p."+propertyToList+" from " + identifiableClass.getName() + " as p", "where upper(p."+propertyToList+") like :" + SmartCombo.FILTER_PARAM_NAME + " order by " + propertyToList, "where p.id = :" + SmartCombo.FILTER_PARAM_NAME); convertToUpper = true; } public String getDiscriminator() { return SmartCombo.class.getName(); } public boolean validate(PageState pageState) throws IOException, ServletException { return true; } public void init(PageContext pageContext) { PageState ps = PageState.getCurrentPageState(); if (!ps.initedElements.contains(getDiscriminator())) { pageContext.getRequest().setAttribute(ACTION, INITIALIZE); super.toHtml(pageContext); ps.initedElements.add(getDiscriminator()); } } public void toHtml(PageContext pageContext) { if (classic) { this.urlToInclude = "/commons/layout/smartCombo/partCombo.jsp"; } else { if (shooted) throw new RuntimeException("You cannot call toHtml twice in case of smartCombo"); shooted = true; init(pageContext); } pageContext.getRequest().setAttribute(ACTION, DRAW_INPUT); super.toHtml(pageContext); } public void toHtmlI18n(PageContext pageContext) { PageState pageState = PageState.getCurrentPageState(); if (label == null) label = I18n.get(fieldName); else // added bicch 17/01/2008 label = I18n.get(label); toHtml(pageContext); } public void addLinkToEntity(PageSeed pageSeed, String toolTip, PageState pageState) { ButtonLink bl = new ButtonLink(pageSeed); Img imgRsrc = new Img(pageState.getSkin().imgPath + "smartComboDetail.gif", "", "", ""); imgRsrc.toolTip = toolTip; linkToEntity = new ButtonImg(bl, imgRsrc); } public static String getHiddenFieldName(String ceName) { return ceName + TEXT_FIELD_POSTFIX; } public List<Object[]> fillResultList(String filter, String hiddenValue) throws PersistenceException { OqlQuery oqlForFiltering = null; List<Object[]> prs = new ArrayList(); // if there is any hidden value fill the first row if (JSP.ex(hiddenValue)) { oqlForFiltering = new OqlQuery(hql + " " + whereForId); // modified R&P for PosgreSql // if (PersistenceConfiguration.getDefaultPersistenceConfiguration().dialect.equals(PostgreSQLDialect.class)) if (PersistenceConfiguration.getDefaultPersistenceConfiguration().dialect.equals(org.hibernate.dialect.PostgreSQLDialectDBBlobs.class)) oqlForFiltering.getQuery().setInteger(SmartCombo.FILTER_PARAM_NAME, Integer.parseInt(hiddenValue + "")); else oqlForFiltering.getQuery().setString(SmartCombo.FILTER_PARAM_NAME, hiddenValue + ""); //oqlForFiltering.setParameter(SmartCombo.FILTER_PARAM_NAME, hiddenValue); List<Object[]> list = oqlForFiltering.list(); if(list.size()>0) prs.add(list.get(0)); } // add additional lines if any if ((JSP.ex(hiddenValue) || !JSP.ex(filter)) && JSP.ex(additionalLines)) { //is the first line is in the additional do not add it again for (Object[] addLine : additionalLines) { highlightedIds.add(addLine[0]); if (prs.size() == 0 || !addLine[0].equals(prs.get(0)[0])) { prs.add(addLine); } } } //copy the elements for check duplicated List<Object[]> alreadyThere = new ArrayList(prs); //if is the first search and there is few lines remove the filter in order to fill with a bunch of lines if (JSP.ex(hiddenValue) && prs.size() < 4) { filter = ""; } if (queryHelperForFiltering != null) { queryHelperForFiltering.setParameter(SmartCombo.FILTER_PARAM_NAME, (searchAll ? "%" + filter + "%" : filter + "%")); oqlForFiltering = queryHelperForFiltering.toHql(); } else { oqlForFiltering = new OqlQuery(hql + " " + whereForFiltering); oqlForFiltering.setParameter(SmartCombo.FILTER_PARAM_NAME, (searchAll ? "%" + filter + "%" : filter + "%")); } if (fixedParams.keySet() != null && fixedParams.keySet().size() > 0) { for (String s : fixedParams.keySet()) { Object value = fixedParams.get(s); if (value instanceof Collection) try { oqlForFiltering.getQuery().setParameterList(s, (Collection) value); } catch (HibernateException e) { throw new PersistenceException(e); } else oqlForFiltering.getQuery().setParameter(s, value); } } oqlForFiltering.getQuery().setMaxResults(maxRowToFetch); List<Object[]> listFromQuery = oqlForFiltering.list(); // fill the result list if the line in not already there for (Object[] addLine : listFromQuery) { boolean found = false; for (Object[] existLine : alreadyThere) { if (addLine[0].equals(existLine[0])) { found = true; break; } } if (!found) { prs.add(addLine); } } return prs; } }