Changeset 5686


Ignore:
Timestamp:
Aug 5, 2011, 2:54:06 PM (10 years ago)
Author:
Nicklas Nordborg
Message:

References #1597: Subtypes of items

Smarter selection of default subtypes and related items (protocols, etc) when new items are created. The 'recently used items' are remembered per subtype so the list should contain more relevant entries when creating items of different subtypes.

Implemented by the sample edit dialog so far. The current implementation more or less prevents the use of project default items (which also depend on the subtype). This need to be fixed and integrate into the current solution.

Location:
trunk
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/ItemContext.java

    r5590 r5686  
    727727 
    728728  /**
     729    Add an item to the "recently used items" list when used for a specific
     730    item subtype. Each type of item+subtype has their own list which may contain at
     731    most <code>maxInList</code> items.
     732    <p>
     733    Note! The lists are stored as string of colon-separated ID:s in
     734    the setting given by the item type. For example the recently used
     735    protocols are found in the setting:
     736    <code>getSetting("PROTOCOL.#subtype-id.recent")</code>. Client code is advised to
     737    not change these settings.
     738   
     739    @param item The item to add
     740    @param subtype The subtype of the parent item (can be null)
     741    @param maxInList The maximum number of items in the list
     742    @since 3.0
     743   */
     744  public void setRecent(BasicItem item, ItemSubtype subtype, int maxInList)
     745  {
     746    if (subtype == null)
     747    {
     748      setRecent(item, maxInList);
     749    }
     750    else
     751    {
     752      setRecent(item, "#"+subtype.getId(), maxInList);
     753    }
     754  }
     755 
     756  /**
    729757    Add an item to the "recently used items" list. Each type of item
    730758    has their own list and sublists which may contain at
     
    783811 
    784812  /**
     813    Get the number of recently used items in the list.
     814    @param itemType The type of items
     815    @param subtype The subtype of the parent item (can be null)
     816    @since 3.0
     817  */
     818  public int getNumRecent(Item itemType, ItemSubtype subtype)
     819  {
     820    if (subtype == null)
     821    {
     822      return getNumRecent(itemType);
     823    }
     824    else
     825    {
     826      return getNumRecent(itemType, "#"+subtype.getId());
     827    }
     828  }
     829 
     830  /**
    785831    Get the number of recently used items in a list with a sublist.
    786832    @param itemType The type of items
     
    816862  {
    817863    return loadRecent(dc, itemType, itemType.name());
     864  }
     865 
     866  /**
     867    Get all recently used items of the specified type. The list
     868    only contains items that the logged in user has read permission
     869    to and that hasn't been deleted. Thus, the {@link #getNumRecent(Item, ItemSubtype)}
     870    may return a larger number than what is actually returned in this
     871    list.
     872   
     873    @param dc The DbControl used to access the database
     874    @param itemType The type of items
     875    @param subtype The subtype of the parent item (can be null)
     876    @return A list with the items
     877    @since 3.0
     878  */
     879  public List<? extends BasicItem> getRecent(DbControl dc, Item itemType, ItemSubtype subtype)
     880  {
     881    if (subtype == null)
     882    {
     883      return getRecent(dc, itemType);
     884    }
     885    else
     886    {
     887      return getRecent(dc, itemType, "#" + subtype.getId());
     888    }
    818889  }
    819890 
     
    923994    setRecent(itemType.name(), null);
    924995  }
    925  
     996
     997  /**
     998    Clear the recently used items list from items of the specified type.
     999    @param itemType The type of items
     1000    @param subtype The subtype of the parent item (can be null)
     1001    @since 3.0
     1002  */
     1003  public void clearRecent(Item itemType, ItemSubtype subtype)
     1004  {
     1005    if (subtype == null)
     1006    {
     1007      clearRecent(itemType);
     1008    }
     1009    else
     1010    {
     1011      clearRecent(itemType, "#" + subtype.getId());
     1012    }
     1013  }
     1014 
     1015
    9261016  /**
    9271017    Clear the recently used items sublist from items of the specified type.
  • trunk/src/core/net/sf/basedb/core/ItemSubtype.java

    r5651 r5686  
    3535import net.sf.basedb.core.query.Expressions;
    3636import net.sf.basedb.core.query.Hql;
     37import net.sf.basedb.core.query.Orders;
    3738import net.sf.basedb.core.query.Restrictions;
    3839import net.sf.basedb.util.ClassUtil;
     
    121122  /**
    122123    Utility method for loading a subtype that is related to the subtype of the given item.
     124    This is more or less equivalent to calling {@link Subtypable#getItemSubtype()} and
     125    then {@link ItemSubtype#getRelatedSubtype(Item)} but with proper null handling and
     126    a possibility to return a default subtype if no related one can be found.
     127   
    123128    @param dc A DbControl to use for database access
    124129    @param item A subtypable item
     
    172177   
    173178    return null;
     179  }
     180 
     181  /**
     182    Utility method for locating all subtypes that have a relationship to the
     183    subtype of the given item. This is more or less equivalent to finding
     184    all subtypes were the method {@link ItemSubtype#getRelatedSubtype(Item)}
     185    return the subtype of the given subtypable item (optionally restricting
     186    to one parent item type).
     187   
     188    @param dc A DbControl to use for database access
     189    @param item A subtypeable item
     190    @param parentType The item type of the subtypes that we want to find or null
     191      to find any subtype
     192    @return A list with the subtypes, or an empty list if none could be found
     193  */
     194  public static List<ItemSubtype> getParentSubtypes(DbControl dc, Subtypable item, Item parentType)
     195  {
     196    ItemSubtype subtype = item.getItemSubtype();
     197    if (subtype == null) return Collections.emptyList();
     198   
     199    ItemQuery<ItemSubtype> query = subtype.getParentSubtypes();
     200    if (parentType != null)
     201    {
     202      query.restrict(Restrictions.eq(Hql.property("itemType"), Expressions.integer(parentType.getValue())));
     203    }
     204    query.order(Orders.asc(Hql.property("name")));
     205    return query.list(dc);
    174206  }
    175207 
  • trunk/www/admin/itemsubtypes/ajax.jsp

    r5650 r5686  
    2626  import="net.sf.basedb.core.DbControl"
    2727  import="net.sf.basedb.core.Item"
     28  import="net.sf.basedb.core.ItemContext"
    2829  import="net.sf.basedb.core.ItemSubtype"
    2930  import="net.sf.basedb.core.Subtypable"
     31  import="net.sf.basedb.core.BasicItem"
     32  import="net.sf.basedb.core.Nameable"
    3033  import="net.sf.basedb.core.InvalidDataException"
    3134  import="net.sf.basedb.util.Values"
     
    3437  import="net.sf.basedb.clients.web.WebException"
    3538  import="org.json.simple.JSONObject"
     39  import="org.json.simple.JSONArray"
     40  import="java.util.List"
    3641%>
    3742<%
     
    8287    dc.close();
    8388  }
     89  else if ("GetRecentAndRelated".equals(cmd))
     90  {
     91    dc = sc.newDbControl();
     92
     93    ItemSubtype subtype = itemId == 0 ? null : ItemSubtype.getById(dc, itemId);
     94    Item mainItemType = subtype == null ? Item.valueOf(request.getParameter("itemType")) : subtype.getMainItemType();
     95    ItemContext cc = sc.getCurrentContext(mainItemType);
     96
     97    if (subtype != null)
     98    {
     99      json.put("id", subtype.getId());
     100      json.put("name", subtype.getName());
     101    }
     102   
     103    for (String relatedType : request.getParameterValues("relatedType"))
     104    {
     105      JSONObject jsonITEM = new JSONObject();
     106      Item relatedItem = Item.valueOf(relatedType);
     107     
     108      // Load the related subtype
     109      if (subtype != null)
     110      {
     111        ItemSubtype relatedSubtype = subtype.getRelatedSubtype(relatedItem);
     112        if (relatedSubtype != null)
     113        {
     114          JSONObject jsonRelated = new JSONObject();
     115          jsonRelated.put("id", relatedSubtype.getId());
     116          jsonRelated.put("name", relatedSubtype.getName());
     117          jsonITEM.put("related", jsonRelated);
     118        }
     119      }
     120     
     121      // Load the most recently used items
     122      List<? extends BasicItem> recentItems = cc.getRecent(dc, relatedItem, subtype);
     123      if (recentItems != null && recentItems.size() > 0)
     124      {
     125        JSONArray jsonRecent = new JSONArray();
     126        for (BasicItem recent : recentItems)
     127        {
     128          JSONObject jsonRecentItem = new JSONObject();
     129          jsonRecentItem.put("id", recent.getId());
     130          jsonRecentItem.put("name", ((Nameable)recent).getName());
     131          jsonRecent.add(jsonRecentItem);
     132        }
     133        jsonITEM.put("recent", jsonRecent);
     134      }
     135      json.put(relatedType, jsonITEM);
     136    }
     137    dc.close();
     138  }
    84139  else
    85140  {
  • trunk/www/admin/itemsubtypes/list_subtypes.jsp

    r5645 r5686  
    9292  Formatter<Date> dateFormatter = FormatterFactory.getDateFormatter(sc);
    9393
     94  ItemQuery<ItemSubtype> relatedQuery = ItemSubtype.getQuery(null);
     95  relatedQuery.join(Hql.innerJoin("parents", "pp"));
     96  relatedQuery.restrict(Restrictions.eq(Hql.alias("pp"), Hql.entityParameter("subtype", Item.ITEMSUBTYPE)));
     97  relatedQuery.order(Orders.asc(Hql.property("name")));
     98  relatedQuery.include(cc.getInclude());
     99 
    94100  Map<Plugin.MainType, Integer> pluginCount = PluginDefinition.countPlugins(dc, guiContext);
    95101  try
     
    258264        exportable="true"
    259265      />
     266      <tbl:columndef
     267        id="relatedTypes"
     268        property="&relatedSubtypes(name)"
     269        datatype="string"
     270        title="Related item types"
     271        filterable="true"
     272        exportable="true"
     273      />     
    260274      <tbl:columndef
    261275        id="description"
     
    423437                <tbl:cell column="systemId"><%=Values.getString(item.getSystemId())%></tbl:cell>
    424438                <tbl:cell column="itemType"><%=item.getMainItemType()%></tbl:cell>
     439                <tbl:cell column="relatedTypes">
     440                  <%
     441                  relatedQuery.setEntityParameter("subtype", item);
     442                  String separator = "";
     443                  for (ItemSubtype related : relatedQuery.list(dc))
     444                  {
     445                    out.write(separator);
     446                    if (mode.hasPropertyLink())
     447                    {
     448                      out.write(Base.getLinkedName(ID, related, false, mode.hasEditLink()));
     449                    }
     450                    else
     451                    {
     452                      out.write(HTML.encodeTags(related.getName()));
     453                    }
     454                    out.write(" (" + related.getMainItemType() + ")");
     455                    separator = ", ";
     456                  }
     457                  %>
     458                </tbl:cell>
    425459                <tbl:cell column="description"><%=HTML.encodeTags(item.getDescription())%></tbl:cell>
    426460              </tbl:row>
  • trunk/www/biomaterials/samples/edit_sample.jsp

    r5664 r5686  
    5353  import="net.sf.basedb.clients.web.util.HTML"
    5454  import="net.sf.basedb.util.Values"
     55  import="net.sf.basedb.util.ListUtil"
    5556  import="net.sf.basedb.util.formatter.WellCoordinateFormatter"
    5657  import="net.sf.basedb.util.formatter.Formatter"
     
    6667  import="java.util.HashSet"
    6768  import="java.util.Date"
     69  import="java.util.Collections"
    6870%>
    6971<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
     
    8991
    9092  boolean readCurrentSubtype = true;
    91   int currentSubtypeId = 0;
     93  ItemSubtype currentSubtype = null;
    9294  boolean readCurrentProtocol = true;
    9395  Protocol currentProtocol = null;
     
    102104  BioPlate currentBioPlate = null;
    103105 
    104   // Load recently used items
    105   List<Protocol> recentProtocols = (List<Protocol>)cc.getRecent(dc, Item.PROTOCOL);
    106   List<BioSource> recentBioSources = (List<BioSource>)cc.getRecent(dc, Item.BIOSOURCE);
    107   List<BioPlate> recentBioPlates = (List<BioPlate>)cc.getRecent(dc, Item.BIOPLATE);
    108  
    109106  WellCoordinateFormatter rowFormatter = new WellCoordinateFormatter(true);
    110107  WellCoordinateFormatter columnFormatter = new WellCoordinateFormatter(false);
     
    124121    }
    125122  }
    126    
    127123
    128124  if (itemId == 0)
     
    130126    title = "Create sample";
    131127    cc.removeObject("item");
    132     currentSubtypeId = Values.getInt(request.getParameter("subtype_id"));
    133     if (currentSubtypeId == 0)
    134     {
    135       int recentSubtypeId = Values.getInt(cc.getRecent(Item.ITEMSUBTYPE.name(), 0));
    136       currentSubtypeId = Values.getInt(cc.getPropertyValue("itemSubtype"), recentSubtypeId);
    137     }
    138128    if (cc.getPropertyFilter("creationEvent.protocol.name") != null)
    139129    {
     
    142132    int bioSourceId = Values.getInt(request.getParameter("biosource_id"));
    143133    int sampleId = Values.getInt(request.getParameter("sample_id"));
     134    int currentSubtypeId = Values.getInt(request.getParameter("subtype_id"));
     135    List<ItemSubtype> relatedToParent = Collections.emptyList();
    144136    if (bioSourceId != 0)
    145137    {
     
    147139      parentType = Item.BIOSOURCE;
    148140      name = currentBioSource.getName() + ".s" + (currentBioSource.countSamples() + 1);
     141      if (currentSubtypeId == 0)
     142      {
     143        relatedToParent = ItemSubtype.getParentSubtypes(dc, currentBioSource, Item.SAMPLE);
     144      }
    149145    }
    150146    else if (sampleId != 0)
     
    155151      samplesQuery = Sample.getQuery();
    156152      samplesQuery.restrict(Restrictions.eq(Hql.property("id"), Expressions.integer(sampleId)));
     153      if (currentSubtypeId == 0)
     154      {
     155        relatedToParent = ItemSubtype.getParentSubtypes(dc, s, Item.SAMPLE);
     156      }
    157157    }
    158158    else if (Values.getBoolean(request.getParameter("pooled")))
     
    167167    {
    168168      name = Values.getString(cc.getPropertyValue("name"), "New sample");
     169    }
     170    if (currentSubtypeId == 0)
     171    {
     172      if (relatedToParent.size() > 0)
     173      {
     174        // Find most recently used related subtype
     175        List<ItemSubtype> recentSubtypes = (List<ItemSubtype>)cc.getRecent(dc, Item.ITEMSUBTYPE);
     176        currentSubtype = ListUtil.findFirstCommon(recentSubtypes, relatedToParent, relatedToParent.get(0));
     177      }
     178      else
     179      {
     180        int recentSubtypeId = Values.getInt(cc.getRecent(Item.ITEMSUBTYPE.name(), 0));
     181        currentSubtypeId = Values.getInt(cc.getPropertyValue("itemSubtype"), recentSubtypeId);
     182        if (currentSubtypeId > 0) currentSubtype = ItemSubtype.getById(dc, currentSubtypeId);
     183      }
    169184    }
    170185    eventDate = (Date)cc.getPropertyObject("creationEvent.eventDate");
     
    185200    try
    186201    {
    187       ItemSubtype subtype = sample.getItemSubtype();
    188       if (subtype != null) currentSubtypeId = subtype.getId();
     202      currentSubtype = sample.getItemSubtype();
    189203    }
    190204    catch (PermissionDeniedException ex)
     
    235249    }
    236250  }
     251 
     252  // Load recently used items
     253  List<Protocol> recentProtocols = (List<Protocol>)cc.getRecent(dc, Item.PROTOCOL, currentSubtype);
     254  List<BioSource> recentBioSources = (List<BioSource>)cc.getRecent(dc, Item.BIOSOURCE);
     255  List<BioPlate> recentBioPlates = (List<BioPlate>)cc.getRecent(dc, Item.BIOPLATE);
    237256 
    238257  // Query to retrieve item types
     
    347366    }
    348367   
     368    function subtypeOnChange()
     369    {
     370      var frm = document.forms['sample'];
     371      var subtypeId = ItemSubtype.getSubtypeId('sample');
     372      var recentInfo = ItemSubtype.getRecentAndRelatedInfo(subtypeId, 'SAMPLE', ['PROTOCOL', 'BIOSOURCE', 'SAMPLE']);
     373      protocolChanged = ItemSubtype.updateRecentItemsInList(frm.protocol_id, recentInfo.PROTOCOL.recent);
     374    }
     375   
    349376    function selectProtocolOnClick()
    350377    {
     
    633660        <td colspan="2">
    634661          <select name="subtype_id"
    635             <%=!readCurrentSubtype ? "disabled readonly class=\"disabled selectionlist\"" : "class=\"selectionlist\""%>>
     662            <%=!readCurrentSubtype ? "disabled readonly class=\"disabled selectionlist\"" : "class=\"selectionlist\""%>
     663            onchange="subtypeOnChange()"
     664            >
    636665          <%
    637666          if (!readCurrentSubtype)
     
    646675            <option value="0">-none-
    647676            <%
     677            int currentSubtypeId = currentSubtype == null ? 0 : currentSubtype.getId();
    648678            for (ItemSubtype subtype : subtypesQuery.list(dc))
    649679            {
  • trunk/www/biomaterials/samples/index.jsp

    r5664 r5686  
    228228      sample.setExternalId(Values.getStringOrNull(request.getParameter("external_id")));
    229229      sample.setOriginalQuantity(Values.getFloat(request.getParameter("original_quantity"), null));
     230
     231      int subtypeId = Values.getInt(request.getParameter("subtype_id"), -1);
     232      ItemSubtype subtype = null;
     233      if (subtypeId >= 0) // < 0 = denied or unchanged
     234      {
     235        if (subtypeId > 0) subtype = ItemSubtype.getById(dc, subtypeId);
     236        sample.setItemSubtype(subtype);
     237        if (subtype != null) cc.setRecent(subtype, maxRecent);
     238      }
    230239     
    231240      BioMaterialEvent creationEvent = sample.getCreationEvent();
     
    239248          Protocol pt = protocolId == 0 ? null : Protocol.getById(dc, protocolId);
    240249          creationEvent.setProtocol(pt);
    241           if (pt != null) cc.setRecent(pt, maxRecent);
     250          if (pt != null) cc.setRecent(pt, subtype, maxRecent);
    242251        }
    243252      }
    244      
    245       int subtypeId = Values.getInt(request.getParameter("subtype_id"), -1);
    246       if (subtypeId >= 0) // < 0 = denied or unchanged
    247       {
    248         ItemSubtype subtype = subtypeId == 0 ? null : ItemSubtype.getById(dc, subtypeId);
    249         sample.setItemSubtype(subtype);
    250         if (subtype != null) cc.setRecent(subtype, maxRecent);
    251       }
    252      
     253 
    253254      int biowellId = Values.getInt(request.getParameter("biowell_id"), -1);
    254255      if (biowellId >= 0) // < 0 = denied or unchanged
  • trunk/www/include/scripts/subtypes.js

    r5650 r5686  
    2828function ItemSubtypeClass()
    2929{
     30 
     31  this.cache = new Array();
     32 
    3033  /**
    3134    Get the subtype of the given item.
     
    6265      return null;
    6366    }
     67   
     68    // Use cached information if we have it
     69    var info = this.cache['ID'+subtypeId];
     70    if (info && info[relatedItemType] && info[relatedItemType].related)
     71    {
     72      return info[relatedItemType].related;
     73    }
     74   
    6475    var request = Ajax.getXmlHttpRequest();
    6576    var ID = getSessionId();
     
    97108  }
    98109 
     110  this.getRecentAndRelatedInfo = function(subtypeId, mainItemType, relatedItemTypes)
     111  {
     112    var info = this.cache['ID' + subtypeId];
     113    if (info)
     114    {
     115      // We have some info already, but...
     116      // ...check if we are missing info about some related item types
     117      var missingInfo = new Array();
     118      for (var i = 0; i < relatedItemTypes.length; i++)
     119      {
     120        if (!info[relatedItemTypes[i]]) missingInfo[missingInfo.length] = relatedItemTypes[i];
     121      }
     122      relatedItemTypes = missingInfo;
     123    }
     124   
     125    // Get more info...
     126    if (relatedItemTypes.length > 0)
     127    {
     128      var request = Ajax.getXmlHttpRequest();
     129      var url = getRoot() + 'admin/itemsubtypes/ajax.jsp?ID=' + getSessionId();
     130      url += '&cmd=GetRecentAndRelated&itemType='+mainItemType;
     131      if (subtypeId)
     132      {
     133        url += '&item_id='+subtypeId;
     134      }
     135      for (var i = 0; i < relatedItemTypes.length; i++)
     136      {
     137        url += '&relatedType=' + relatedItemTypes[i];
     138      }
     139      request.open("GET", url, false);
     140      request.send(null);
     141      var moreInfo = JSON.parse(request.responseText);
     142      if (!info)
     143      {
     144        info = moreInfo;
     145      }
     146      else
     147      {
     148        // Merge the new info with the existing info
     149        for (var i = 0; i < relatedItemTypes.length; i++)
     150        {
     151          info[relatedItemTypes[i]]= moreInfo[relatedItemTypes[i]];
     152        }
     153      }
     154      this.cache['ID'+subtypeId] = info;
     155    }
     156    return info;
     157  }
     158 
     159  this.updateRecentItemsInList = function(list, recentItems, noNoneOption)
     160  {
     161    if (!list || !recentItems) return;
     162   
     163    var oldSelectedValue = list.selectedIndex >= 0 ? list[list.selectedIndex].value : 0;
     164    var reselectItemId = 0;
     165   
     166    // Find the current 'recently used' option in the list
     167    // It should be the first entry which has a value == 0
     168    // ignoring the 'none' option if present
     169    var recentHeaderIndex = -1;
     170    for (var i = noNoneOption ? 0 : 1; i < list.length; i++)
     171    {
     172      if (list[i].value == 0)
     173      {
     174        // Found it -- remove all items below it
     175        // but remember the ID of the currently selected
     176        // item if it is removed (so we can select it again)
     177        recentHeaderIndex = i;
     178        if (list.selectedIndex > i)
     179        {
     180          reselectItemId = list[list.selectedIndex].value;
     181        }
     182        list.length = i+1;
     183      }
     184    }
     185   
     186    // If index == -1 we didn't find it so we must add 'recently used'
     187    if (recentHeaderIndex == -1)
     188    {
     189      var recentHeader = new Option('- recently used -', 0);
     190      recentHeader.className = 'recentheader';
     191      recentHeader.disabled = true;
     192      recentHeaderIndex = list.length;
     193      list[recentHeaderIndex] = recentHeader;
     194    }
     195   
     196    // Add the new items to the end of the list
     197    for (var i = 0; i < recentItems.length; i++)
     198    {
     199      var selected = reselectItemId == recentItems[i].id;
     200      list[list.length] = new Option((i+1) + '. ' + recentItems[i].name, recentItems[i].id, false, selected);
     201    }
     202   
     203    // If no item is selected, select the first 'recently used' item.
     204    if (list.selectedIndex == 0)
     205    {
     206      list.selectedIndex = recentHeaderIndex+1;
     207    }
     208   
     209    var currentSelectedValue = list.selectedIndex >= 0 ? list[list.selectedIndex].value : 0;
     210    return oldSelectedValue != currentSelectedValue;
     211  }
    99212 
    100213}
Note: See TracChangeset for help on using the changeset viewer.