Changeset 8083


Ignore:
Timestamp:
Oct 20, 2022, 2:56:36 PM (11 months ago)
Author:
Nicklas Nordborg
Message:

Merge BASE 3.19.4 to the trunk.

Location:
trunk
Files:
75 edited
6 copied

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/doc/src/docbook/user/webclient.xml

    r7982 r8083  
    23792379        <variablelist>
    23802380        <varlistentry>
    2381           <term><guilabel>Item type</guilabel></term>
     2381          <term><guilabel>Parent/child</guilabel></term>
    23822382          <listitem>
    23832383            <para>
     
    23932393            <para>
    23942394              This list will automatically be updated with all available subtypes
    2395               for the selected main item type. It is not possible to select items
    2396               without a subtype.
     2395              for the selected parent or child item type. It is not possible to select
     2396              items without a subtype.
    23972397            </para>
    23982398          </listitem>
    23992399        </varlistentry>
    2400 
     2400       
    24012401        <varlistentry>
    2402           <term><guilabel>Column</guilabel></term>
    2403           <listitem>
    2404             <para>
    2405               Once the subtype has been selected, this list will display all possible
    2406               columns that can be added to the table. Most regular properties such as
     2402          <term><guilabel>with link to</guilabel></term>
     2403          <listitem>
     2404            <para>
     2405              This is an optional selection that is only available if a parent item type has
     2406              been selected in the left list. In this list is possible to select
     2407              an item type that should be a child type of the selected parent type.
     2408              A subtype for the child item must also be selected.
     2409            </para>
     2410          </listitem>
     2411        </varlistentry>
     2412
     2413        <varlistentry>
     2414          <term><guilabel>Columns/Annotations/Extensions</guilabel></term>
     2415          <listitem>
     2416            <para>
     2417              When the parent/child selection contains a valid combination,
     2418              the table will display all possible columns that can be selected
     2419              and added to the table. Most regular properties such as
    24072420              <guilabel>Name</guilabel> and <guilabel>Registration date</guilabel>
    2408               can be selected, as well as all annotation types that has been defined
    2409               and linked to the selected subtype (marked with <guilabel>[A]</guilabel>).
     2421              can be selected and are displayed in the <guilabel>Columns</guilabel>
     2422              column. All annotation types that has been defined and linked to the
     2423              selected subtype are displayed in the <guilabel>Annotations</guilabel>
     2424              column. It is also possible for external extensions to add more columns
     2425              that can be selected in the <guilabel>Extensions</guilabel> column.
    24102426            </para>
    24112427          </listitem>
     
    24282444          <para>
    24292445            Use the <guibutton>Add</guibutton> button to add columns to the table.
    2430             It is possible change the main item type and subtype to add more columns.
    2431             When you are done, use the &gbClose; button to close the dialog.
     2446            It is possible change the parent/child item type and subtype to add more
     2447            columns. When you are done, use the &gbClose; button to close the dialog.
    24322448          </para>
    24332449       
  • trunk/src/clients/web/net/sf/basedb/clients/web/Base.java

    r7982 r8083  
    3737import net.sf.basedb.core.Platform;
    3838import net.sf.basedb.core.PlatformVariant;
     39import net.sf.basedb.core.Project;
    3940import net.sf.basedb.core.Protocol;
    4041import net.sf.basedb.core.ReporterList;
     
    655656          cc.getSelected().add(Values.getInt(itemId));
    656657        }
    657        
    658         String allColumns = cc.getSetting("columns");
    659         Set<String> visibleParentCols = new HashSet<>();
    660         if (allColumns != null)
    661         {
    662           for (String col : allColumns.split(","))
    663           {
    664             if (col.startsWith("/")) visibleParentCols.add(col);
    665           }
    666         }
    667658
    668659        while (names.hasMoreElements())
     
    684675              property = property.substring(0, split);
    685676            }
    686             if (property.startsWith("/") && value != null)
    687             {
    688               // If the filter is on a parent item and the column is no
    689               // longer visible, we need to remove the filter, since it
    690               // will get very confusing otherwise
    691               // There are some variants when column is the name of a parent item
    692               boolean isOkFilter = visibleParentCols.contains(property)
    693                 || visibleParentCols.contains(property.replace("/name", "/."))
    694                 || visibleParentCols.contains(property.replace(".name", ""));
    695               if (!isOkFilter) value = null;
    696             }
    697677            if (value != null)
    698678            {
     
    15211501    // Modified annotations
    15221502    String modified = Values.getStringOrNull(request.getParameter("modifiedAnnotations"));
     1503    int projectId = Values.getInt(request.getParameter("project_id"), -1);
     1504    Project project = projectId > 0 ? Project.getById(dc, projectId) : null;
    15231505    if (newItem == null) newItem = oldItem;
    15241506    if (modified != null)
     
    15601542        if (annotationId != null)
    15611543        {
    1562           a = Annotation.getById(dc, annotationId.intValue());
     1544          a = Annotation.getById(dc, annotationId.intValue(), projectId >= 0);
    15631545         
    15641546          if ("DELETE".equals(modifyType))
     
    15661548            if (source == Annotation.Source.PRIMARY)
    15671549            {
    1568               newAs.removeAnnotation(at);
     1550              if (projectId >= 0)
     1551              {
     1552                newAs.removeProjectAnnotation(at, project);
     1553              }
     1554              else
     1555              {
     1556                newAs.removeAnnotation(at);
     1557              }
    15691558            }
    15701559            else
     
    15991588          if (!"DELETE".equals(modifyType))
    16001589          {
    1601             a = newAs.getAnnotation(at);
     1590            a = projectId >= 0 ? newAs.getProjectAnnotation(at, project) : newAs.getAnnotation(at);
    16021591          }
    16031592        }
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/JspContext.java

    r8045 r8083  
    200200 
    201201  /**
     202    Add several scripts in one go. If the collection is null or empty
     203    nothing is done.
     204    @since 3.19.4
     205  */
     206  public void addScripts(Collection<String> scriptsToAdd)
     207  {
     208    if (scriptsToAdd == null || scriptsToAdd.isEmpty()) return;
     209    if (scripts == null) scripts = new HashMap<>();
     210    getSet(scripts, null).addAll(scriptsToAdd);
     211    if (needResourcesPerExtension)
     212    {
     213      getSet(scripts, getCurrentExtension().getId()).addAll(scriptsToAdd);
     214    }
     215  }
     216 
     217  /**
    202218    Get the Set that is stored under the given key. If
    203219    no Set exists it is created and stored in the map.
     
    241257    }
    242258  }
     259 
     260  /**
     261    Add several stylesheets in one go. If the collection is null or empty
     262    nothing is done.
     263    @since 3.19.4
     264  */
     265  public void addStylesheets(Collection<String> stylesheetsToAdd)
     266  {
     267    if (stylesheetsToAdd == null || stylesheetsToAdd.isEmpty()) return;
     268    if (stylesheets == null) stylesheets = new HashMap<>();
     269    getSet(stylesheets, null).addAll(stylesheetsToAdd);
     270    if (needResourcesPerExtension)
     271    {
     272      getSet(stylesheets, getCurrentExtension().getId()).addAll(stylesheetsToAdd);
     273    }
     274  }
    243275
    244276  /**
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/AbstractListColumnBean.java

    r7908 r8083  
    2121*/
    2222package net.sf.basedb.clients.web.extensions.list;
     23
     24import java.util.Collection;
    2325
    2426import net.sf.basedb.core.DbControl;
     
    6567  private Enumeration<String, String> enumeration;
    6668  private Formatter<? super V> formatter;
     69  private Formatter<Collection<? super V>> collectionFormatter;
    6770 
    6871  public AbstractListColumnBean()
     
    272275  {
    273276    this.exportFormatter = exportFormatter;
     277  }
     278 
     279  @Override
     280  public Formatter<Collection<? super V>> getCollectionFormatter()
     281  {
     282    return collectionFormatter == null ? ListColumnAction.super.getCollectionFormatter() : collectionFormatter;
     283  }
     284  public void setCollectionFormatter(Formatter<Collection<? super V>> collectionFormatter)
     285  {
     286    this.collectionFormatter = collectionFormatter;
    274287  }
    275288 
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/AnyLinkColumnActionFactory.java

    r7874 r8083  
    88import net.sf.basedb.clients.web.extensions.fileviewer.FileViewerContext;
    99import net.sf.basedb.clients.web.extensions.fileviewer.FileViewerUtil;
     10import net.sf.basedb.clients.web.extensions.list.RelatedItemColumn.Specification;
    1011import net.sf.basedb.core.BasicItem;
    1112import net.sf.basedb.core.DbControl;
     
    4748    String subContext = Values.getString(guiContext.getSubContext(), "");
    4849   
    49     ItemContext cc = sc.getCurrentContext(item, subContext, null);
    50     if (cc == null) return false;
    51     String allColumns = cc.getSetting("columns");
     50    String allColumns = null;
     51    if (RelatedItemExtensionColumn.SUBCONTEXT.equals(subContext))
     52    {
     53      // The current item should be a Specification instance that may specify
     54      // the column. Otherwise we load the columns from the regular list context
     55      // which makes it possible to select linked item columns from a parent/child
     56      // that are currently available
     57      Specification spec = jspContext.getCurrentItem();
     58      if (spec != null) allColumns = spec.actionId;
     59      subContext = "";
     60    }
     61    if (allColumns == null)
     62    {
     63      ItemContext cc = sc.getCurrentContext(item, subContext, null);
     64      if (cc == null) return false;
     65      allColumns = cc.getSetting("columns");
     66    }
    5267    if (allColumns == null || !allColumns.contains("|")) return false;
    5368
     
    6681    Item item = guiContext.getItem();
    6782    String subContext = Values.getString(guiContext.getSubContext(), "");
    68    
    69     ItemContext cc = sc.getCurrentContext(item, subContext, null);
    70     if (cc == null) return null;
    71    
    72     String allColumns = cc.getSetting("columns");
     83    String allColumns = null;
     84    if (RelatedItemExtensionColumn.SUBCONTEXT.equals(subContext))
     85    {
     86      // The current item should be a Specification instance that may specify
     87      // the column. Otherwise we load the columns from the regular list context
     88      Specification spec = jspContext.getCurrentItem();
     89      if (spec != null) allColumns = spec.actionId;
     90      subContext = "";
     91    }
     92    if (allColumns == null)
     93    {
     94      ItemContext cc = sc.getCurrentContext(item, subContext, null);
     95      if (cc == null) return null;
     96      allColumns = cc.getSetting("columns");
     97    }
    7398    if (allColumns == null || !allColumns.contains("|")) return null;
    7499
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/ListColumnAction.java

    r7908 r8083  
    2222package net.sf.basedb.clients.web.extensions.list;
    2323
     24import java.util.Collection;
     25
    2426import net.sf.basedb.core.DbControl;
    2527import net.sf.basedb.core.Type;
    2628import net.sf.basedb.util.Enumeration;
    2729import net.sf.basedb.util.extensions.Action;
     30import net.sf.basedb.util.formatter.CollectionFormatter;
    2831import net.sf.basedb.util.formatter.Formatter;
    2932
     
    209212 
    210213  /**
     214    Get a formatter that is intended to format a collection of values
     215    that have been retrieved from this action. The default implementation
     216    will return a formatter that creates a comma-separated string of
     217    the individual values where each value is formatted with the formatter
     218    from {@link #getFormatter()}.
     219    @since 3.19.4
     220  */
     221  @SuppressWarnings({ "rawtypes", "unchecked" })
     222  public default Formatter<Collection<? super V>> getCollectionFormatter()
     223  {
     224    return new CollectionFormatter(getFormatter());
     225  }
     226 
     227  /**
    211228    Get the value that should be displayed in the column. This method is
    212229    called once for every item that is listed in the table. The returned
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/ListColumnUtil.java

    r7605 r8083  
    3737import net.sf.basedb.core.plugin.GuiContext;
    3838import net.sf.basedb.util.extensions.ExtensionPointBean;
     39import net.sf.basedb.util.extensions.ExtensionsFilter;
    3940import net.sf.basedb.util.extensions.ExtensionsInvoker;
    4041import net.sf.basedb.util.extensions.Registry;
     
    9596
    9697  /**
     98    @see #useExtensions(JspContext, ExtensionsFilter)
     99  */
     100  public static <I> ExtensionsInvoker<ListColumnAction<I, ?>> useExtensions(JspContext jspContext)
     101  {
     102    return useExtensions(jspContext, null);
     103  }
     104
     105  /**
    97106    Use list column extensions for a given gui context. This method
    98107    will assemble all specific and generic extension points and
    99     then call {@link ExtensionsControl#useExtensions(JspContext, String...)}.
     108    then call {@link ExtensionsControl#useExtensions(JspContext, ExtensionsFilter, String...)}
     109    or {@link ExtensionsControl#useExtensions(JspContext, String...)} (if the filter is null)
    100110   
    101111    @param jspContext The current jsp context
     112    @param filter Optional filter for the extensions
    102113    @return An invoker instance
     114    @since 3.19.4
    103115  */
    104   public static <I> ExtensionsInvoker<ListColumnAction<I, ?>> useExtensions(JspContext jspContext)
     116  public static <I> ExtensionsInvoker<ListColumnAction<I, ?>> useExtensions(JspContext jspContext, ExtensionsFilter filter)
    105117  {
    106118    String[] ep = new String[6]; // 6 is the maximum number of extension points
     
    130142    }
    131143    ep[index++] = EP_PREFIX + itemType.name().toLowerCase();
    132     return ExtensionsControl.useExtensions(jspContext, ep);
     144    return filter == null ? ExtensionsControl.useExtensions(jspContext, ep) : ExtensionsControl.useExtensions(jspContext, filter, ep);
    133145  }
    134146 
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemAnnotationColumn.java

    r7893 r8083  
    55import java.util.Set;
    66
     7import net.sf.basedb.clients.web.formatter.FormatterFactory;
    78import net.sf.basedb.clients.web.util.HTML;
    89import net.sf.basedb.core.Annotatable;
    910import net.sf.basedb.core.AnnotationType;
    10 import net.sf.basedb.core.Item;
    11 import net.sf.basedb.core.SyncFilter.SourceItemTransform;
    12 import net.sf.basedb.core.query.Restriction;
     11import net.sf.basedb.core.DbControl;
    1312import net.sf.basedb.core.snapshot.AnnotationSnapshot;
    1413import net.sf.basedb.core.snapshot.AnnotationTypeFilter;
    1514import net.sf.basedb.util.Enumeration;
    1615import net.sf.basedb.util.filter.Filter;
     16import net.sf.basedb.util.formatter.CollectionFormatter;
     17import net.sf.basedb.util.listable.SourceItemTransformer;
    1718
    1819/**
     
    2829  private final Filter<AnnotationSnapshot> atFilter;
    2930 
    30   RelatedItemAnnotationColumn(int index, String id, Item sourceType, Item targetType, SourceItemTransform direction, Restriction targetRestriction, RelatedItemHelper helper, AnnotationType at)
     31  @SuppressWarnings({ "unchecked", "rawtypes" })
     32  RelatedItemAnnotationColumn(DbControl dc, int index, Specification spec, RelatedItemHelper helper, AnnotationType at)
    3133  {
    32     super(index, id, sourceType, targetType, direction, targetRestriction, helper);
     34    super(dc, index, spec, helper);
    3335    this.at = at;
    3436    this.atFilter = new AnnotationTypeFilter(at);
     37    setTitle(spec.generateTitle(at.getName() + " [A]"));
     38    setTooltip(spec.generateTooltip(at.getName()));
     39    setValueType(at.getValueType());
     40    setExportFormatter(FormatterFactory.getTypeFormatter(dc.getSessionControl(), at.getValueType()));
     41    if (!helper.lazy) setFormatter(new CollectionFormatter(getExportFormatter()));
     42
    3543    if (at.isEnumeration())
    3644    {
     
    4856 
    4957  @Override
    50   public Object getValue(RelatedItemHelper helper, Annotatable item)
     58  public Object getValue(RelatedItemHelper helper, Annotatable item, SourceItemTransformer preTransform)
    5159  {
    52     Set<Annotatable> items = getRelatedItems(item);
     60    Set<Annotatable> items = getRelatedItems(item, preTransform);
    5361    if (items.size() == 0) return null;
    5462   
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemColumn.java

    r7893 r8083  
    22
    33import java.util.Collections;
    4 import java.util.HashMap;
    5 import java.util.Map;
    64import java.util.Set;
    75import java.util.TreeSet;
    86
    9 import net.sf.basedb.clients.web.Base;
    10 import net.sf.basedb.clients.web.formatter.FormatterFactory;
    11 import net.sf.basedb.clients.web.formatter.LinkedItemFormatter;
    127import net.sf.basedb.core.Annotatable;
    138import net.sf.basedb.core.AnnotationType;
     
    1510import net.sf.basedb.core.Item;
    1611import net.sf.basedb.core.ItemList;
    17 import net.sf.basedb.core.ItemQuery;
    1812import net.sf.basedb.core.ItemSubtype;
    1913import net.sf.basedb.core.Metadata;
    2014import net.sf.basedb.core.RawDataTypes;
    21 import net.sf.basedb.core.Type;
     15import net.sf.basedb.core.StringUtil;
    2216import net.sf.basedb.core.query.Expressions;
    2317import net.sf.basedb.core.query.Hql;
     
    2721import net.sf.basedb.core.RawDataType;
    2822import net.sf.basedb.core.SyncFilter.SourceItemTransform;
    29 import net.sf.basedb.util.Enumeration;
    3023import net.sf.basedb.util.NameableComparator;
    3124import net.sf.basedb.util.Values;
     25import net.sf.basedb.util.extensions.ExtensionsInvoker;
    3226import net.sf.basedb.util.filter.Filter;
    33 import net.sf.basedb.util.formatter.CollectionFormatter;
    34 import net.sf.basedb.util.formatter.NameableFormatter;
    35 import net.sf.basedb.util.formatter.ToStringFormatter;
    3627import net.sf.basedb.util.listable.ListableUtil;
    3728import net.sf.basedb.util.listable.RestrictionTransformer;
     
    5142 
    5243  /**
    53     For now, we assume that 'expr' is /DIRECTION/ITEMTYPE/subtype-id/#annotationtype-id or
     44    For now, we assume that 'expr' is one of:
     45    /DIRECTION/ITEMTYPE/subtype-id/#annotationtype-id
     46    /DIRECTION/ITEMTYPE/subtype-id/!extension-id#action-id
    5447    /DIRECTION/ITEMTYPE/subtype-id/property
    5548   
     
    5851  */
    5952  @SuppressWarnings({ "unchecked", "rawtypes" })
    60   public static RelatedItemColumn create(DbControl dc, int index, Item sourceType, String expr, RelatedItemHelper helper)
     53  public static RelatedItemColumn create(DbControl dc, int index, Specification spec, RelatedItemHelper helper)
    6154  {
    6255    RelatedItemColumn col = null;
    6356    try
    6457    {
    65       String[] tmp = expr.split("/", 5);
    66       SourceItemTransform direction = SourceItemTransform.CHILD_TO_PARENT;
    67       int baseIndex = 1;
    68       if (tmp.length == 5)
    69       {
    70         baseIndex = 2;
    71         direction = "CHILD".equals(tmp[1]) ? SourceItemTransform.PARENT_TO_CHILD : SourceItemTransform.CHILD_TO_PARENT;
    72       }
    73       Item targetType = Item.valueOf(tmp[baseIndex]);
    74       Filter<? super ItemList> itemListFilter = null;
    75       Restriction targetRestriction = null;
    76       String subtypeName = null;
    77       if (targetType == Item.RAWBIOASSAY)
    78       {
    79         RawDataType rdt = RawDataTypes.getSafeRawDataType(tmp[baseIndex+1]);
    80         subtypeName = rdt.getName();
    81         targetRestriction = Restrictions.eq(Hql.property("rawDataType"), Expressions.string(rdt.getId()));
    82         itemListFilter = new RawDataTypeFilter(rdt);
     58      if (spec.property.startsWith("/"))
     59      {
     60        // This is a multi-hop path: child --> parent --> child
     61        if (spec.direction != SourceItemTransform.CHILD_TO_PARENT)
     62        {
     63          throw new RuntimeException("First multi-hop must be to /PARENT: " + spec.expression);
     64        }
     65        Specification nextSpec = Specification.parse(spec.targetType, spec.property);
     66        if (nextSpec.property.startsWith("/"))
     67        {
     68          throw new RuntimeException("More than two PARENT/CHILD hops are not allowed: " + spec.expression);
     69        }
     70        if (nextSpec.direction != SourceItemTransform.PARENT_TO_CHILD)
     71        {
     72          throw new RuntimeException("Last multi-hop must be to /CHILD: " + spec.expression);
     73        }
     74        RelatedItemColumn nextHop = create(dc, index, nextSpec, helper);
     75        col = new RelatedItemMultiHopColumn(dc, index, spec, helper, nextHop);
     76      }
     77      else if (spec.property.startsWith("#"))
     78      {
     79        AnnotationType at = AnnotationType.getById(dc, spec.annotationId);
     80        col = new RelatedItemAnnotationColumn(dc, index, spec, helper, at);
     81      }
     82      else if (spec.property.startsWith("!"))
     83      {
     84        ListColumnAction <Annotatable, ?> action = null;
     85        // Get the invoker that was saved by the RelatedItemColumnActionFactory.prepareContext() method
     86        ExtensionsInvoker<ListColumnAction<Annotatable, ?>> invoker = helper.jspContext.getAttribute("RelatedItemInvoker."+spec.expression);
     87       
     88        if (invoker != null)
     89        {
     90          invoker.getClientContext().setCurrentItem(spec);
     91          for (ListColumnAction<Annotatable, ?> lca : invoker)
     92          {
     93            if (spec.actionId.equals(lca.getId()) || spec.actionId.equals(lca.getFilterProperty()))
     94            {
     95              action = lca;
     96              break;
     97            }
     98          }
     99        }
     100        if (action == null) throw new RuntimeException("Extension not found: "+Values.getString(spec.extensionId)+"#"+spec.actionId);
     101        col = new RelatedItemExtensionColumn(dc, index, spec, helper, action);
     102      }
     103      else if (spec.property.equals("§itemLists"))
     104      {
     105        Filter<? super ItemList> itemListFilter = null;
     106        if (spec.targetType == Item.RAWBIOASSAY)
     107        {
     108          itemListFilter = new RawDataTypeFilter(spec.getRawDataType());
     109        }
     110        else
     111        {
     112          itemListFilter = new SubtypeFilter(spec.getItemSubtype(dc));
     113        }
     114        col = new RelatedItemListColumn(dc, index, spec, itemListFilter, helper);
    83115      }
    84116      else
    85117      {
    86         ItemSubtype subtype = ItemSubtype.getById(dc, Values.getInt(tmp[baseIndex+1]));
    87         subtypeName = subtype.getName();
    88         targetRestriction = Restrictions.eq(Hql.property("itemSubtype"), Hql.entity(subtype));
    89         itemListFilter = new SubtypeFilter(subtype);
    90       }
    91       String propSpec = tmp[baseIndex+2];
    92       if (propSpec.startsWith("#"))
    93       {
    94         AnnotationType at = AnnotationType.getById(dc, Values.getInt(propSpec.substring(1)));
    95         col = new RelatedItemAnnotationColumn(index, expr, sourceType, targetType, direction, targetRestriction, helper, at);
    96         col.setTitle(subtypeName+"."+at.getName() + " [A]");
    97         col.setTooltip(subtypeName + " › "+at.getName());
    98         col.setValueType(at.getValueType());
    99         col.setClazz("relateditemcol");
    100         col.setExportFormatter(FormatterFactory.getTypeFormatter(dc.getSessionControl(), at.getValueType()));
    101         if (!helper.lazy) col.setFormatter(new CollectionFormatter(col.getExportFormatter()));
    102       }
    103       else if (propSpec.equals("§itemLists"))
    104       {
    105         col = new RelatedItemListColumn(dc, index, expr, sourceType, targetType, direction, targetRestriction, itemListFilter, helper);
    106         col.setTitle(subtypeName+".Item list");
    107         col.setValueType(Type.INT);
    108         col.setClazz("relateditemcol");
    109         col.setExportFormatter(new NameableFormatter());
    110         if (!helper.lazy) col.setFormatter(new CollectionFormatter(new LinkedItemFormatter()));
    111       }
    112       else
    113       {
    114118        PropertyPath property = null;
    115         if (".".equals(propSpec)) // special case '.' is equal to the "parent" item
    116         {
    117           col = new RelatedItemPropertyColumn(index, expr, sourceType, targetType, direction, targetRestriction, helper, null);
    118           col.setTitle(subtypeName);
    119           col.setTooltip(subtypeName);
    120         }
    121         else
    122         {
    123           Metadata m = Metadata.getInstance(targetType.getItemClass());
    124           property = m.getPropertyPath(propSpec, false);
    125           col = new RelatedItemPropertyColumn(index, expr, sourceType, targetType, direction, targetRestriction, helper, property);
    126           col.setTitle(subtypeName+"."+pathToTitle(propSpec));
    127           col.setTooltip(subtypeName + " › "+propSpec);
    128         }
    129         col.setClazz("relateditemcol");
    130         if (property == null || property.getHibernateType().isEntityType())
    131         {
    132           col.setExportFormatter(new NameableFormatter());
    133           if (!helper.lazy) col.setFormatter(new CollectionFormatter(new LinkedItemFormatter()));
    134           if ("job.itemSubtype".equals(propSpec))
    135           {
    136             ItemQuery<ItemSubtype> jobSubtypesQuery = Base.getSubtypesQuery(Item.JOB);
    137             col.setEnumeration(Enumeration.fromItems(jobSubtypesQuery.list(dc), null));
    138             col.setValueType(Type.INT);
    139           }
    140           else
    141           {
    142             if (col.getFilterProperty()==null)
    143             {
    144               // Add '.name' to make filtering work
    145               String nameProperty = col.getProperty() + ".name";
    146               if (nameProperty.contains("/..")) nameProperty = nameProperty.replace("/..", "/");
    147               col.setFilterProperty(nameProperty);
    148             }
    149             col.setValueType(Type.STRING);
    150           }
    151         }
    152         else
    153         {
    154           Type colType = Type.fromHibernateType(property.getHibernateType());
    155           if (colType != null)
    156           {
    157             col.setValueType(colType);
    158             col.setExportFormatter(FormatterFactory.getTypeFormatter(dc.getSessionControl(), colType));
    159           }
    160           else
    161           {
    162             // Last fallback -- if we get here the Type.fromHibernateType() should be update with special cases
    163             col.setValueType(Type.STRING);
    164             col.setExportFormatter(new ToStringFormatter<>());
    165           }
    166           if (!helper.lazy) col.setFormatter(new CollectionFormatter(col.getExportFormatter()));
    167         }
    168       }
    169       col.setClazz(col.getClazz() + (direction == SourceItemTransform.CHILD_TO_PARENT ? " parentitem" : " childitem"));
    170       col.setTooltip((direction == SourceItemTransform.CHILD_TO_PARENT ? "Parent › " : "Child › ")+col.getTooltip());
     119        if (!".".equals(spec.property)) // special case '.' is equal to the "related" item
     120        {
     121          Metadata m = Metadata.getInstance(spec.targetType.getItemClass());
     122          property = m.getPropertyPath(spec.property, false);
     123        }
     124        col = new RelatedItemPropertyColumn(dc, index, spec, helper, property);
     125      }
    171126    }
    172127    catch (RuntimeException ex)
    173128    {
    174129      ex.printStackTrace(System.out);
    175     }
    176    
    177    
     130      col = new RelatedItemErrorColumn(index, spec, helper, ex.getMessage());
     131
     132    }
    178133    return col;
    179134  }
    180135
    181   private static Map<String, String> pathTitles = new HashMap<>();
    182  
    183   static
    184   {
    185     // See also add_relateditem_columns.js
    186     pathTitles.put("externalId", "External ID");
    187     pathTitles.put("bioWell.bioPlate", "Bioplate");
    188     pathTitles.put("entryDate", "Registered");
    189     pathTitles.put("remainingQuantity", "Remaining quantity");
    190     pathTitles.put("originalQuantity", "Original quantity");
    191     pathTitles.put("creationEvent.entryDate", "Registration date");
    192     pathTitles.put("creationEvent.eventDate", "Creation date");
    193     pathTitles.put("creationEvent.protocol", "Protocol");
    194     pathTitles.put("creationEvent.hardware", "Hardware");
    195     pathTitles.put("protocol", "Protocol");
    196     pathTitles.put("hardware", "Hardware");
    197     pathTitles.put("software", "Software");
    198     pathTitles.put("tag", "Tag");
    199     pathTitles.put("job.itemSubtype", "Job type");
    200     pathTitles.put("$itemLists", "Item lists");
    201   }
    202  
    203   /**
    204     Convert some paths to a more user-friendly title instead.
    205   */
    206   private static String pathToTitle(String path)
    207   {
    208     String title = pathTitles.get(path);
    209     return title == null ? path.substring(0, 1).toUpperCase() + path.substring(1) : title;
    210   }
    211  
    212136  private final int index;
    213   private final Item sourceType;
    214   private final Item targetType;
     137  private final Specification spec;
    215138  private final RelatedItemHelper helper;
    216   private final SourceItemTransformerFactory transformerFactory;
    217139  private final SourceItemTransformer transformer;
    218140 
    219   RelatedItemColumn(int index, String id, Item sourceType, Item targetType, SourceItemTransform direction, Restriction targetRestriction, RelatedItemHelper helper)
     141  RelatedItemColumn(DbControl dc, int index, Specification spec, RelatedItemHelper helper)
    220142  {
    221143    this.index = index;
    222144    this.helper = helper;
    223     this.sourceType = sourceType;
    224     this.targetType = targetType;
    225     this.transformerFactory = ListableUtil.getTransformerFactory(targetType);
    226     SourceItemTransformer tmp = transformerFactory.create(sourceType, direction);
    227     this.transformer = targetRestriction == null ? tmp : new RestrictionTransformer(tmp, targetRestriction);
    228     setId(id);
    229     setProperty(id);
     145    this.spec = spec;
     146    if (dc != null)
     147    {
     148      SourceItemTransformerFactory transformerFactory = ListableUtil.getTransformerFactory(spec.targetType);
     149      SourceItemTransformer tmp = transformerFactory.create(spec.sourceType, spec.direction);
     150      Restriction targetRestriction = spec.createTargetTypeRestriction(dc);
     151      if (targetRestriction != null) tmp = new RestrictionTransformer(tmp, targetRestriction);
     152      this.transformer = tmp;
     153    }
     154    else
     155    {
     156      this.transformer = null;
     157    }
     158    setId(spec.expression);
     159    setProperty(spec.expression);
    230160    setExportable(true);
    231161    setFilterable(true);
    232   }
    233 
     162    setClazz("relateditemcol" +
     163        (spec.direction == SourceItemTransform.CHILD_TO_PARENT ? " parentitem" : " childitem")+
     164        (spec.multiHop ? " multihop" : "")
     165        );
     166  }
     167
     168  /**
     169    Is this column lazy-loaded? The default implementation
     170    use the requested laza flag from the helper instance.
     171    @since 3.19.4
     172  */
     173  protected boolean isLazy()
     174  {
     175    return helper.lazy;
     176  }
     177 
     178  /**
     179    Get the transformer implementation that is used to turn source item into target items.
     180    @since 3.19.4
     181  */
     182  protected SourceItemTransformer getTransformer()
     183  {
     184    return transformer;
     185  }
     186 
    234187  /**
    235188    We finalize this implementation to make sure that the helper
    236189    implementation always get a chance to re-cycle transactions.
    237     Subclasses should implement {@link #getValue(RelatedItemHelper, Annotatable)}
     190    Subclasses should implement {@link #getValue(RelatedItemHelper, Annotatable, SourceItemTransformer)}
    238191    to return the requested value from the item and use the
    239192    DbControl from {@link RelatedItemHelper#dc}.
     
    242195  public final Object getValue(DbControl dc, Annotatable item)
    243196  {
    244     if (helper.lazy) return getProxyTag(item);
     197    if (isLazy()) return getProxyTag(item);
    245198    item = helper.recycle(dc, item);
    246     return getValue(helper, item);
     199    return getValue(helper, item, null);
    247200  }
    248201
     
    267220    We finalize this implementation to make sure that the helper
    268221    implementation always get a chance to re-cycle transactions.
    269     Subclasses should implement {@link #getValue(RelatedItemHelper, Annotatable)}
     222    Subclasses should override {@link #getExportValue(RelatedItemHelper, Annotatable, SourceItemTransformer)}
    270223    to return the requested value from the item and use the
    271224    DbControl from {@link RelatedItemHelper#dc}.
     
    275228  {
    276229    item = helper.recycle(dc, item);
    277     return getValue(helper, item);
    278   }
    279 
    280  
    281   /**
    282     Alternate implementation for loading data.
    283   */
    284   protected abstract Object getValue(RelatedItemHelper helper, Annotatable item);
     230    return getExportValue(helper, item, null);
     231  }
     232
     233 
     234  /**
     235    Alternate implementation for loading data. The item is normally an item of the
     236    expected source type, but if a preTransform is present, the item may be of
     237    different type and must be processed with the {@link SourceItemTransformer#transform(net.sf.basedb.util.listable.TransformContext, Set)}
     238    method first. In most cases, implementation should simply call {@link #getRelatedItems(Annotatable, SourceItemTransformer)}
     239    to load the final target items.
     240  */
     241  protected abstract Object getValue(RelatedItemHelper helper, Annotatable item, SourceItemTransformer preTransform);
     242 
     243  /**
     244    Alternate implementation for loading data. The default implementation
     245    simply call the {@link #getValue(RelatedItemHelper, Annotatable, SourceItemTransformer)} method.
     246    @since 3.19.4
     247  */
     248  protected Object getExportValue(RelatedItemHelper helper, Annotatable item, SourceItemTransformer preTransform)
     249  {
     250    return getValue(helper, item, preTransform);
     251  }
    285252 
    286253  /**
     
    288255  */
    289256  @SuppressWarnings({ "unchecked", "rawtypes" })
    290   protected Set<Annotatable> getRelatedItems(Annotatable item)
    291   {
    292     Set<Integer> relatedIds = transformer.transform(helper.transformContext, Collections.singleton(item.getId()));
     257  protected Set<Annotatable> getRelatedItems(Annotatable item, SourceItemTransformer preTransform)
     258  {
     259    Set<Integer> sourceIds = Collections.singleton(item.getId());
     260    if (preTransform != null)
     261    {
     262      sourceIds = preTransform.transform(helper.transformContext, sourceIds);
     263    }
     264    Set<Integer> relatedIds = transformer.transform(helper.transformContext, sourceIds);
    293265   
    294266    Set<Annotatable> relatedItems = new TreeSet<Annotatable>(new NameableComparator(false));
    295267    for (Integer id : relatedIds)
    296268    {
    297       Annotatable a = targetType.getById(helper.dc, id);
     269      Annotatable a = spec.targetType.getById(helper.dc, id);
    298270      relatedItems.add(a);
    299271    }
     
    301273  }
    302274 
     275  /**
     276    Represents a specification for loading a related parent or child item.
     277    The general format is: /DIRECTION/ITEMTYPE/subtype-id/property
     278   
     279    where
     280    * DIRECTION: PARENT or CHILD
     281    * ITEMTYPE: Item.name() value for the related item
     282    * subtype-id: Id of an {@link ItemSubtype} or a {@link RawDataType}
     283    * property: a specification of what value to retrieve from the related item.
     284      It can have different formats:
     285      - #annotationtype-id: Id of an annotation type
     286      - !extension-id#action-id: Id of an extension and {@link ListColumnAction} to use for loading the value
     287      Otherwise it is interpreted as a property that is defined on the targeted instance.
     288   
     289    @since 3.19.4
     290  */
     291  public static class Specification
     292  {
     293   
     294    public static Specification parse(Item sourceType, String expression)
     295    {
     296      Specification spec = new Specification();
     297      spec.sourceType = sourceType;
     298      spec.expression = expression;
     299      spec.directionRaw = "PARENT";
     300      spec.direction = SourceItemTransform.CHILD_TO_PARENT;
     301     
     302      String[] tmp = expression.split("/", 5);
     303      int baseIndex = 1;
     304      if (tmp.length == 5)
     305      {
     306        baseIndex = 2;
     307        spec.directionRaw = tmp[1];
     308        if ("CHILD".equals(spec.directionRaw)) spec.direction = SourceItemTransform.PARENT_TO_CHILD;
     309      }
     310      spec.targetType = Item.valueOf(tmp[baseIndex]);
     311      spec.subtype = tmp[baseIndex+1];
     312      spec.property = tmp[baseIndex+2];
     313     
     314      if (spec.property.startsWith("/"))
     315      {
     316        spec.multiHop = true;
     317      }
     318      else if (spec.property.startsWith("#"))
     319      {
     320        spec.annotationId = Values.getInt(spec.property.substring(1));
     321      }
     322      else if (spec.property.startsWith("!"))
     323      {
     324        // The normal property format is: !extension-id#action-id
     325        // but it may be a filter specification: !x.action-id
     326        // or (but it should never happen): !action-id
     327        if (spec.property.startsWith("!x."))
     328        {
     329          spec.actionId = spec.property.substring(3);
     330        }
     331        else
     332        {
     333          int splitAt = spec.property.indexOf('#');
     334          if (splitAt > 0)
     335          {
     336            spec.extensionId = Values.getStringOrNull(spec.property.substring(1, splitAt));
     337            spec.actionId = spec.property.substring(splitAt+1);
     338          }
     339          else
     340          {
     341            spec.actionId = spec.property.substring(1);
     342          }
     343        }
     344      }
     345     
     346      return spec;
     347    }
     348   
     349    String expression;
     350    String directionRaw;
     351    SourceItemTransform direction;
     352    Item sourceType;
     353    Item targetType;
     354    String subtype;
     355    String subtypeName;
     356    String property;
     357    int annotationId;
     358    String extensionId;
     359    String actionId;
     360    boolean multiHop;
     361   
     362    /**
     363      Get the full expression that defines the targeted item and property.
     364    */
     365    public String getExpression()
     366    {
     367      return expression;
     368    }
     369
     370    /**
     371      Get the raw value of the DIRECTION.
     372    */
     373    public String getDirectionRaw()
     374    {
     375      return directionRaw;
     376    }
     377
     378    /**
     379      Get the parsed value of the DIRECTION.
     380    */
     381    public SourceItemTransform getDirection()
     382    {
     383      return direction;
     384    }
     385
     386    /**
     387      Get the source item type. This is not part of the expression, but
     388      is taken from the current list.
     389    */
     390    public Item getSourceType()
     391    {
     392      return sourceType;
     393    }
     394
     395    /**
     396      Get the target ITEMTYPE from the expression.
     397    */
     398    public Item getTargetType()
     399    {
     400      return targetType;
     401    }
     402
     403    /**
     404      Get the target subtype from the expression.
     405    */
     406    public String getSubtype()
     407    {
     408      return subtype;
     409    }
     410
     411    /**
     412      Get the name of the targeted subtype. This may not always
     413      be available.
     414    */
     415    public String getSubtypeName()
     416    {
     417      return subtypeName;
     418    }
     419
     420    /**
     421      Get the property from the expression.
     422    */
     423    public String getProperty()
     424    {
     425      return property;
     426    }
     427
     428    /**
     429      Get the annotation id from the expression. Only available if the
     430      property represents an annotation (it starts with '#').
     431    */
     432    public int getAnnotationId()
     433    {
     434      return annotationId;
     435    }
     436
     437    /**
     438      Get the extension id from the expression. Only available if the
     439      property represents an extension (it starts with '!').
     440    */
     441    public String getExtensionId()
     442    {
     443      return extensionId;
     444    }
     445
     446    /**
     447      Get the action id from the expression. Only available if the
     448      property represents an extension (it starts with '!').
     449    */
     450    public String getActionId()
     451    {
     452      return actionId;
     453    }
     454   
     455    /**
     456      If the property starts with a '/' it is a multi-hop specification.
     457      A multi-hop specification must be from child->parent->child and must
     458      not have more than two hops.
     459    */
     460    public boolean isMultiHop()
     461    {
     462      return multiHop;
     463    }
     464   
     465    /**
     466      Generates a title using target item type and subtype as prefix.
     467    */
     468    public String generateTitle(String title)
     469    {
     470      String result = "<span class=\"";
     471      result += direction == SourceItemTransform.CHILD_TO_PARENT ? "parentitem" : "childitem";
     472      if (multiHop) result += " multihop";
     473      result += "\">";
     474      result += StringUtil.coalesce(subtypeName, targetType.name());
     475      result += "</span>";
     476      result += withPrefix(".", title);
     477      return result;
     478    }
     479   
     480    /**
     481      Generates a tooltip using target item type and subtype as prefix.
     482    */
     483    public String generateTooltip(String tooltip)
     484    {
     485      return (direction == SourceItemTransform.CHILD_TO_PARENT ? "Parent › " : "Child › ") +
     486          StringUtil.coalesce(subtypeName, targetType.name()) + withPrefix(" › ", tooltip);
     487    }
     488   
     489    /**
     490      Return prefix+string if string is not null or the empty string otherwise.
     491    */
     492    private String withPrefix(String prefix, String s)
     493    {
     494      return s == null ? "" : prefix+s;
     495    }
     496
     497    public ItemSubtype getItemSubtype(DbControl dc)
     498    {
     499      ItemSubtype st = ItemSubtype.getById(dc, Values.getInt(subtype));
     500      subtypeName = st.getName();
     501      return st;
     502    }
     503   
     504    public RawDataType getRawDataType()
     505    {
     506      RawDataType rdt = RawDataTypes.getSafeRawDataType(subtype);
     507      subtypeName = rdt.getName();
     508      return rdt;
     509    }
     510   
     511    public Restriction createTargetTypeRestriction(DbControl dc)
     512    {
     513      Restriction r = null;
     514      if (targetType == Item.RAWBIOASSAY)
     515      {
     516        RawDataType rdt = getRawDataType();
     517        r = Restrictions.eq(Hql.property("rawDataType"), Expressions.string(rdt.getId()));
     518      }
     519      else
     520      {
     521        ItemSubtype subtype = getItemSubtype(dc);
     522        r = Restrictions.eq(Hql.property("itemSubtype"), Hql.entity(subtype));
     523      }
     524      return r;
     525    }
     526
     527  }
     528 
    303529}
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemColumnActionFactory.java

    r7874 r8083  
    55
    66import net.sf.basedb.clients.web.extensions.AbstractJspActionFactory;
     7import net.sf.basedb.clients.web.extensions.ExtensionsControl;
    78import net.sf.basedb.clients.web.extensions.JspContext;
     9import net.sf.basedb.clients.web.extensions.list.RelatedItemColumn.Specification;
    810import net.sf.basedb.core.Annotatable;
    911import net.sf.basedb.core.DbControl;
     
    1214import net.sf.basedb.core.Metadata.PropertyPath;
    1315import net.sf.basedb.core.SessionControl;
     16import net.sf.basedb.core.SyncFilter.SourceItemTransform;
    1417import net.sf.basedb.core.plugin.GuiContext;
    1518import net.sf.basedb.util.Values;
     19import net.sf.basedb.util.extensions.ExtensionsFilter;
     20import net.sf.basedb.util.extensions.ExtensionsInvoker;
    1621import net.sf.basedb.util.extensions.InvokationContext;
     22import net.sf.basedb.util.extensions.SingleExtensionFilter;
    1723
    1824/**
     
    4955    String subContext = Values.getString(guiContext.getSubContext(), "");
    5056   
    51     ItemContext cc = sc.getCurrentContext(item, subContext, null);
    52     if (cc == null) return false;
    53    
    54     String allColumns = cc.getSetting("columns");
     57    String allColumns = null;
     58    SourceItemTransform requiredDirection = null;
     59    if (RelatedItemExtensionColumn.SUBCONTEXT.equals(subContext))
     60    {
     61      // The current item should be a Specification instance that may specify
     62      // the column. Otherwise we load the columns from the regular list context
     63      // which makes it possible to select linked item columns from a parent/child
     64      // that are currently available
     65      Specification spec = jspContext.getCurrentItem();
     66      if (spec != null)
     67      {
     68        allColumns = spec.actionId;
     69        if (spec.direction == SourceItemTransform.CHILD_TO_PARENT)
     70        {
     71          // We may only select CHILD columns from a PARENT
     72          requiredDirection = SourceItemTransform.PARENT_TO_CHILD;
     73          subContext = "";
     74        }
     75      }
     76    }
     77    if (allColumns == null)
     78    {
     79      ItemContext cc = sc.getCurrentContext(item, subContext, null);
     80      if (cc == null) return false;
     81      allColumns = cc.getSetting("columns");
     82    }
    5583    if (allColumns == null || !allColumns.contains("/")) return false;
    5684
    57     jspContext.addScript(jspContext.getRoot() + "/include/scripts/lazy-items.js");
     85    boolean lazy = !Boolean.FALSE.equals(jspContext.getAttribute("lazy-loading"));
     86    if (lazy)
     87    {
     88      jspContext.addScript(jspContext.getRoot() + "/include/scripts/lazy-items.js");
     89    }
     90   
     91    for (String col : allColumns.split(","))
     92    {
     93      if (col.startsWith("/") && col.contains("!"))
     94      {
     95        Specification spec = Specification.parse(item, col);
     96        if (requiredDirection != null && requiredDirection != spec.direction) continue;
     97        if (spec.property.startsWith("/"))
     98        {
     99          // We support multi-hop in max two steps
     100          spec = Specification.parse(spec.targetType, spec.property);
     101        }
     102       
     103        // Create a child context for invoking the specified extensions as if we are on the relevant list page
     104        GuiContext childGuiContext = GuiContext.list(spec.targetType, RelatedItemExtensionColumn.SUBCONTEXT);
     105        JspContext childJspContext = ExtensionsControl.createContext(jspContext.getDbControl(), jspContext.getPageContext(), childGuiContext, spec);
     106        childJspContext.linkAttributes(jspContext);
     107        ExtensionsFilter filter = spec.extensionId == null ? null : new SingleExtensionFilter(spec.extensionId);
     108        ExtensionsInvoker<ListColumnAction<Annotatable, ?>> childInvoker = ListColumnUtil.useExtensions(childJspContext, filter);
     109
     110        if (childInvoker.getNumExtensions() > 0)
     111        {
     112          // Copy scripts and stylesheets to the main JSP context
     113          jspContext.addScripts(childJspContext.getScripts());
     114          jspContext.addStylesheets(childJspContext.getStylesheets());
     115         
     116          // Save the invoker in the main JSP context so that we can use the same
     117          // instance in getActions()->RelatedItemColumn.create()
     118          jspContext.setAttribute("RelatedItemInvoker."+spec.expression, childInvoker);
     119        }
     120      }
     121    }
     122   
    58123    return super.prepareContext(context);
    59124  }
     
    69134    Item item = guiContext.getItem();
    70135    String subContext = Values.getString(guiContext.getSubContext(), "");
    71    
     136
     137    String allColumns = null;
     138    SourceItemTransform requiredDirection = null;
     139    if (RelatedItemExtensionColumn.SUBCONTEXT.equals(subContext))
     140    {
     141      // The current item should be a Specification instance that may specify
     142      // the column. Otherwise we load the columns from the regular list context
     143      Specification spec = jspContext.getCurrentItem();
     144      if (spec != null)
     145      {
     146        allColumns = spec.actionId;
     147        if (spec.direction == SourceItemTransform.CHILD_TO_PARENT)
     148        {
     149          // We may only select CHILD columns from a PARENT
     150          requiredDirection = SourceItemTransform.PARENT_TO_CHILD;
     151          subContext = "";
     152        }
     153      }
     154    }
    72155    ItemContext cc = sc.getCurrentContext(item, subContext, null);
    73     if (cc == null) return null;
    74    
    75     String allColumns = cc.getSetting("columns");
     156    if (allColumns == null)
     157    {
     158      if (cc == null) return null;
     159      allColumns = cc.getSetting("columns");
     160    }
    76161    if (allColumns == null || !allColumns.contains("/")) return null;
    77    
    78162   
    79163    boolean lazy = !Boolean.FALSE.equals(jspContext.getAttribute("lazy-loading"));
    80164    DbControl dc = jspContext.getDbControl();
    81     RelatedItemHelper helper = new RelatedItemHelper(sc, lazy);
     165    RelatedItemHelper helper = new RelatedItemHelper(sc, jspContext, lazy);
    82166    List<ListColumnAction<Annotatable, Object>> actions = new ArrayList<>();
    83167    for (String col : allColumns.split(","))
     
    85169      if (col.startsWith("/"))
    86170      {
    87         ListColumnAction<Annotatable, Object> action = RelatedItemColumn.create(dc, actions.size(), item, col, helper);
     171        Specification spec = Specification.parse(item, col);
     172        if (requiredDirection != null && requiredDirection != spec.direction) continue;
     173        ListColumnAction<Annotatable, Object> action = RelatedItemColumn.create(dc, actions.size(), spec, helper);
    88174        if (action != null) actions.add(action);
    89175      }
     
    91177   
    92178    if (actions.size() == 0) return null;
    93    
     179    // If we are lazy-loading child/parent items we need to store context attributes
     180    // so that the lazy-loading feature can pick them up again
     181    if (lazy) jspContext.storeAttributes(cc, "lazy-related-items-attributes");
    94182    return actions.toArray(new ListColumnAction[actions.size()]);
    95183  }
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemHelper.java

    r7962 r8083  
    44import java.util.Set;
    55
     6import net.sf.basedb.clients.web.extensions.JspContext;
    67import net.sf.basedb.core.Annotatable;
    78import net.sf.basedb.core.DbControl;
     
    2425 
    2526  final SessionControl sc;
     27  final JspContext jspContext;
    2628  final boolean lazy;
    2729  // Use the snapshot manager for efficient loading of annotations
     
    3234  TransformContext transformContext;
    3335 
    34   RelatedItemHelper(SessionControl sc, boolean lazy)
     36  RelatedItemHelper(SessionControl sc, JspContext jspContext, boolean lazy)
    3537  {
    3638    this.sc = sc;
     39    this.jspContext = jspContext;
    3740    this.lazy = lazy;
    3841    this.manager = new SnapshotManager();
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemListColumn.java

    r7893 r8083  
    77
    88import net.sf.basedb.clients.web.Base;
     9import net.sf.basedb.clients.web.formatter.LinkedItemFormatter;
    910import net.sf.basedb.core.Annotatable;
    1011import net.sf.basedb.core.DbControl;
    11 import net.sf.basedb.core.Item;
    1212import net.sf.basedb.core.ItemContext;
    1313import net.sf.basedb.core.ItemList;
    1414import net.sf.basedb.core.ItemQuery;
    15 import net.sf.basedb.core.SyncFilter.SourceItemTransform;
    1615import net.sf.basedb.core.Type;
    1716import net.sf.basedb.core.query.Expressions;
    1817import net.sf.basedb.core.query.Hql;
    19 import net.sf.basedb.core.query.Restriction;
    2018import net.sf.basedb.core.query.Restrictions;
    2119import net.sf.basedb.util.NameableComparator;
    2220import net.sf.basedb.util.filter.Filter;
     21import net.sf.basedb.util.formatter.CollectionFormatter;
     22import net.sf.basedb.util.formatter.NameableFormatter;
     23import net.sf.basedb.util.listable.SourceItemTransformer;
    2324
    2425/**
     
    3536  private final ItemQuery<ItemList> listQuery;
    3637 
    37   RelatedItemListColumn(DbControl dc, int index, String id, Item sourceType, Item targetType, SourceItemTransform direction, Restriction targetRestriction, Filter<? super ItemList> itemListFilter, RelatedItemHelper helper)
     38  @SuppressWarnings({ "unchecked", "rawtypes" })
     39  RelatedItemListColumn(DbControl dc, int index, Specification spec, Filter<? super ItemList> itemListFilter, RelatedItemHelper helper)
    3840  {
    39     super(index, id, sourceType, targetType, direction, targetRestriction, helper);
    40     ItemContext cc = dc.getSessionControl().getCurrentContext(sourceType);
    41     setEnumeration(Base.getItemListsEnum(dc, targetType, cc.getInclude(), itemListFilter));
     41    super(dc, index, spec, helper);
     42    ItemContext cc = dc.getSessionControl().getCurrentContext(spec.sourceType);
     43    setEnumeration(Base.getItemListsEnum(dc, spec.targetType, cc.getInclude(), itemListFilter));
     44    setTitle(spec.generateTitle("Item list"));
     45    setTooltip(spec.generateTooltip("Item list"));
     46    setValueType(Type.INT);
     47    setExportFormatter(new NameableFormatter());
     48    if (!helper.lazy) setFormatter(new CollectionFormatter(new LinkedItemFormatter()));
    4249
    4350    listQuery = ItemList.getQuery();
    4451    listQuery.setIncludes(cc.getInclude());
    4552    listQuery.join(Hql.innerJoin("members", "m"));
    46     listQuery.restrict(Restrictions.eq(Hql.property("memberType"), Expressions.integer(targetType.getValue())));
     53    listQuery.restrict(Restrictions.eq(Hql.property("memberType"), Expressions.integer(spec.targetType.getValue())));
    4754    listQuery.restrict(Restrictions.in(Hql.alias("m"), Expressions.parameter("itemId", Type.INT)));
    4855  }
    4956 
    5057  @Override
    51   public Object getValue(RelatedItemHelper helper, Annotatable item)
     58  public Object getValue(RelatedItemHelper helper, Annotatable item, SourceItemTransformer preTransform)
    5259  {
    53     Set<Annotatable> items = getRelatedItems(item);
     60    Set<Annotatable> items = getRelatedItems(item, preTransform);
    5461    if (items.size() == 0) return null;
    5562
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/list/RelatedItemPropertyColumn.java

    r7893 r8083  
    22
    33import java.util.ArrayList;
     4import java.util.HashMap;
    45import java.util.List;
     6import java.util.Map;
    57import java.util.Set;
    68
     9import net.sf.basedb.clients.web.Base;
     10import net.sf.basedb.clients.web.formatter.FormatterFactory;
     11import net.sf.basedb.clients.web.formatter.LinkedItemFormatter;
    712import net.sf.basedb.core.Annotatable;
     13import net.sf.basedb.core.DbControl;
    814import net.sf.basedb.core.Item;
     15import net.sf.basedb.core.ItemQuery;
     16import net.sf.basedb.core.ItemSubtype;
    917import net.sf.basedb.core.Metadata;
     18import net.sf.basedb.core.Type;
    1019import net.sf.basedb.core.Metadata.PropertyPath;
    11 import net.sf.basedb.core.SyncFilter.SourceItemTransform;
    12 import net.sf.basedb.core.query.Restriction;
     20import net.sf.basedb.util.Enumeration;
     21import net.sf.basedb.util.formatter.CollectionFormatter;
     22import net.sf.basedb.util.formatter.NameableFormatter;
     23import net.sf.basedb.util.formatter.ToStringFormatter;
     24import net.sf.basedb.util.listable.SourceItemTransformer;
    1325
    1426/**
     
    2335{
    2436
     37 
     38  private static Map<String, String> pathTitles = new HashMap<>();
     39  static
     40  {
     41    // See also add_relateditem_columns.js
     42    pathTitles.put("externalId", "External ID");
     43    pathTitles.put("bioWell.bioPlate", "Bioplate");
     44    pathTitles.put("entryDate", "Registered");
     45    pathTitles.put("remainingQuantity", "Remaining quantity");
     46    pathTitles.put("originalQuantity", "Original quantity");
     47    pathTitles.put("creationEvent.entryDate", "Registration date");
     48    pathTitles.put("creationEvent.eventDate", "Creation date");
     49    pathTitles.put("creationEvent.protocol", "Protocol");
     50    pathTitles.put("creationEvent.hardware", "Hardware");
     51    pathTitles.put("protocol", "Protocol");
     52    pathTitles.put("hardware", "Hardware");
     53    pathTitles.put("software", "Software");
     54    pathTitles.put("tag", "Tag");
     55    pathTitles.put("job.itemSubtype", "Job type");
     56    pathTitles.put("$itemLists", "Item lists");
     57  }
     58 
     59  /**
     60    Convert some paths to a more user-friendly title instead.
     61  */
     62  private static String pathToTitle(String path)
     63  {
     64    String title = pathTitles.get(path);
     65    return title == null ? path.substring(0, 1).toUpperCase() + path.substring(1) : title;
     66  }
     67 
    2568  private final PropertyPath<? super Annotatable, ?> property;
    2669 
    27   RelatedItemPropertyColumn(int index, String id, Item sourceType, Item targetType, SourceItemTransform direction, Restriction targetRestriction, RelatedItemHelper helper, PropertyPath<? super Annotatable, ?> property)
     70  @SuppressWarnings({ "rawtypes", "unchecked" })
     71  RelatedItemPropertyColumn(DbControl dc, int index, Specification spec, RelatedItemHelper helper, PropertyPath<? super Annotatable, ?> property)
    2872  {
    29     super(index, id, sourceType, targetType, direction, targetRestriction, helper);
     73    super(dc, index, spec, helper);
    3074    this.property = property;
     75    if (property == null)
     76    {
     77      setTitle(spec.generateTitle(null));
     78      setTooltip(spec.generateTooltip(null));
     79    }
     80    else
     81    {
     82      setTitle(spec.generateTitle(pathToTitle(spec.property)));
     83      setTooltip(spec.generateTooltip(spec.property));
     84    }
     85    // If the property ends with an item...
     86    if (property == null || property.getHibernateType().isEntityType())
     87    {
     88      setExportFormatter(new NameableFormatter());
     89      if (!helper.lazy) setFormatter(new CollectionFormatter(new LinkedItemFormatter()));
     90      // Specical case for Job subtypes which we set as an enumeration of existing subtypes
     91      if ("job.itemSubtype".equals(spec.property))
     92      {
     93        ItemQuery<ItemSubtype> jobSubtypesQuery = Base.getSubtypesQuery(Item.JOB);
     94        setEnumeration(Enumeration.fromItems(jobSubtypesQuery.list(dc), null));
     95        setValueType(Type.INT);
     96      }
     97      else
     98      {
     99        // Filtering is made on the "name"
     100        if (getFilterProperty()==null)
     101        {
     102          // Add '.name' to make filtering work
     103          String nameProperty = getProperty() + ".name";
     104          if (nameProperty.contains("/..")) nameProperty = nameProperty.replace("/..", "/");
     105          setFilterProperty(nameProperty);
     106        }
     107        setValueType(Type.STRING);
     108      }
     109    }
     110    else
     111    {
     112      Type colType = Type.fromHibernateType(property.getHibernateType());
     113      if (colType != null)
     114      {
     115        setValueType(colType);
     116        setExportFormatter(FormatterFactory.getTypeFormatter(dc.getSessionControl(), colType));
     117      }
     118      else
     119      {
     120        // Last fallback -- if we get here the Type.fromHibernateType() should be update with special cases
     121        setValueType(Type.STRING);
     122        setExportFormatter(new ToStringFormatter<>());
     123      }
     124      if (!helper.lazy) setFormatter(new CollectionFormatter(getExportFormatter()));
     125    }
     126
    31127  }
    32128 
    33129  @Override
    34   public Object getValue(RelatedItemHelper helper, Annotatable item)
     130  public Object getValue(RelatedItemHelper helper, Annotatable item, SourceItemTransformer preTransform)
    35131  {
    36     Set<Annotatable> items = getRelatedItems(item);
     132    Set<Annotatable> items = getRelatedItems(item, preTransform);
    37133    if (items.size() == 0) return null;
    38134
  • trunk/src/clients/web/net/sf/basedb/clients/web/taglib/table/ColumnDef.java

    r7943 r8083  
    618618    JSONObject json = new JSONObject();
    619619    json.put("id", getId());
    620     json.put("title", getTitle());
     620    json.put("title", HTML.stripMarkup(getTitle()));
     621    json.put("titleHTML", getTitle());
    621622    json.put("subtitle", getSubtitle());
    622623    json.put("property", getProperty());
     
    696697      {
    697698        context = table.getSc().getCurrentContext(table.getItem(), table.getSubcontext());
    698         // Check if hidden columns have a filter
    699         if (!isVisible && context != null)
     699        String filterProp = getFilterproperty();
     700        if (context != null)
    700701        {
    701           if (isPermission)
     702          // If the filter property is not the same as the ID, we should keep
     703          // a pointer between them so we can match in Base.getAndSetCurrentContext()
     704          // when testing if a filter is present on a hidden column
     705          if (filterProp != null && !filterProp.equals(getId()))
    702706          {
    703             hasFilter = context.getItemPermission() != null;
     707            context.setObject("id2filter."+getId(), filterProp);
    704708          }
    705           else if (getFilterproperty() != null)
     709         
     710          // Check if hidden columns have a filter
     711          if (!isVisible)
    706712          {
    707             hasFilter = context.hasPropertyFilter(getFilterproperty());
     713            if (isPermission)
     714            {
     715              hasFilter = context.getItemPermission() != null;
     716            }
     717            else if (filterProp != null)
     718            {
     719              hasFilter = context.hasPropertyFilter(filterProp);
     720            }
    708721          }
    709722        }
     
    731744        if (!isSortable)
    732745        {
    733           String theTooltip = getTooltip() != null ? getTooltip() : getTitle();
     746          String theTooltip = getTooltip() != null ? getTooltip() : HTML.stripMarkup(getTitle());
    734747          sb.append("<span title=\"").append(theTooltip).append("\">");
    735748          sb.append(getTitle());
     
    747760
    748761          String theTooltip = getTooltip() != null ?
    749             getTooltip() : "Sort by "+getTitle() + "; hold CTRL, ALT or SHIFT to sort by multiple columns";
     762            getTooltip() : "Sort by "+HTML.stripMarkup(getTitle()) + "; hold CTRL, ALT or SHIFT to sort by multiple columns";
    750763          sb.append("<span class=\"link table-sort\"");
    751764          sb.append(" id=\"").append(idPrefix).append(".sort\"");
     
    922935      if (isFilterable && hasFilter)
    923936      {
    924         table.addFilteredColumn(getId(), isVisible);
     937        table.addFilteredColumn(getFilterproperty(), isVisible);
    925938      }
    926939    }
  • trunk/src/clients/web/net/sf/basedb/clients/web/taglib/table/PresetSelector.java

    r7407 r8083  
    4040import net.sf.basedb.clients.web.util.HTML;
    4141import net.sf.basedb.util.Values;
    42 
    43 import java.util.List;
     42import net.sf.basedb.util.formatter.Formatter;
     43
     44import java.util.Set;
     45
    4446import javax.servlet.jsp.JspException;
    4547import javax.servlet.jsp.JspTagException;
     
    305307    {
    306308      sb.append("");
    307       List<String> hiddenFilteredColumns = table.getHiddenFilteredColumns();
     309      Set<String> hiddenFilteredColumns = table.getHiddenFilteredColumns();
    308310      int numHidden = hiddenFilteredColumns == null ? 0 : hiddenFilteredColumns.size();
    309311      if (numHidden > 0)
    310312      {
    311         String columns = Values.getString(hiddenFilteredColumns, ",", true);
     313        String columns = Values.getString(hiddenFilteredColumns, ",", true,
     314          new Formatter<>()
     315          {
     316            @Override
     317            public String format(String value)
     318            {
     319              if (value.startsWith("/"))
     320              {
     321                if (value.endsWith("/name") || value.endsWith(".name"))
     322                {
     323                  value = value.replace("/name", "/.");
     324                }
     325                else if (value.endsWith(".name"))
     326                {
     327                  value = value.replace(".name", "");
     328                }
     329                else if (value.contains("/!x."))
     330                {
     331                  // This is an extension filter convert to /!#
     332                  value = value.replace("/!x.", "/!#");
     333                }
     334              }
     335              else if (value.startsWith("!x."))
     336              {
     337                value = value.substring(3);
     338              }
     339              return value;
     340            }
     341
     342            @Override
     343            public String parseString(String value)
     344            {
     345              return null;
     346            }
     347          });
    312348        sb.append("<span class=\"link\" id=\"").append(table.getId()).append(".showcolumns\"");
    313349        sb.append(" data-hidden-columns=\"").append(columns).append("\"");
  • trunk/src/clients/web/net/sf/basedb/clients/web/taglib/table/Table.java

    r7982 r8083  
    3939import java.util.LinkedHashSet;
    4040import java.util.List;
    41 import java.util.LinkedList;
    4241import java.util.Arrays;
     42import java.util.Collection;
    4343import java.util.Set;
    4444
     
    326326  private Map<String, String> columnSubtitle;
    327327  private transient Map<String, Formatter<?>> columnFormatter;
    328   private List<String> hiddenFilteredColumns;
     328  private Set<String> hiddenFilteredColumns;
    329329 
    330330  @SuppressWarnings("rawtypes")
     
    568568  {
    569569    numFilteredColumns++;
    570     if (!visible) hiddenFilteredColumns.add(columnId);
    571   }
    572   List<String> getHiddenFilteredColumns()
     570    if (visible) hiddenFilteredColumns.remove(columnId);
     571  }
     572  Set<String> getHiddenFilteredColumns()
    573573  {
    574574    return hiddenFilteredColumns;
     
    720720    columnFilter = new HashMap<String, String>();
    721721    columnFormatter = new HashMap<String, Formatter<?>>();
    722     hiddenFilteredColumns = new LinkedList<String>();
     722    hiddenFilteredColumns = new LinkedHashSet<String>();
    723723    jsonColumnDefs = new JSONArray();
    724724    if ((columns == null) || columns.equals("all"))
     
    751751      {
    752752        addHidden("exclude", (String)exclude);
     753      }
     754     
     755      // Until we know more, assume that all filtered columns are hidden...
     756      Collection<net.sf.basedb.core.PropertyFilter> filters = cc.getPropertyFilters();
     757      if (filters != null)
     758      {
     759        for (net.sf.basedb.core.PropertyFilter pf : filters)
     760        {
     761          hiddenFilteredColumns.add(pf.getProperty());
     762        }
    753763      }
    754764    }
  • trunk/src/core/common-queries.xml

    r7997 r8083  
    27522752  <query id="DBLOG_GET_ANNOTATIONTYPE_INFO" type="HQL">
    27532753    <sql>
    2754       SELECT at.name, at.valueType, at.disableLogOfValues
     2754      SELECT at.name, at.valueType, at.disableLogOfValues, at.projectAnnotations
    27552755      FROM AnnotationTypeData at
    27562756      WHERE at.id = :annotationTypeId
    27572757    </sql>
    27582758    <description>
    2759       An HQL query that loads the 'name', 'valueType' and 'disableLogOfValues' flag
    2760       for an annotation type given the ID.
     2759      An HQL query that loads the 'name', 'valueType', 'disableLogOfValues' and
     2760      the 'projectAnnotations' flag for an annotation type given the ID.
    27612761    </description>
    27622762  </query>
  • trunk/src/core/net/sf/basedb/core/Annotation.java

    r7643 r8083  
    147147    Annotation a = dc.loadItem(Annotation.class, id);
    148148    if (a == null) throw new ItemNotFoundException("Annotation[id="+id+"]");
     149    return a;
     150  }
     151 
     152  /**
     153    Get an <code>Annotation</code> item when you know the id
     154    and ignore the rules for project-specific annotations when
     155    modifying the values.
     156    @see #setValuesIfDifferent(List, Unit)
     157    @since 3.19.4
     158  */
     159  public static Annotation getById(DbControl dc, int id, boolean ignoreActiveProject)
     160      throws ItemNotFoundException, PermissionDeniedException, BaseException
     161  {
     162    Annotation a = getById(dc, id);
     163    a.ignoreActiveProject = ignoreActiveProject;
    149164    return a;
    150165  }
  • trunk/src/core/net/sf/basedb/core/AnnotationBatcher.java

    r7522 r8083  
    10681068   
    10691069    /**
     1070      Get the id of the project this annotation belong to.
     1071      Only relevant if the annotation type is using project-specific
     1072      annotations.
     1073      @since 3.19.4
     1074    */
     1075    public int getProjectId()
     1076    {
     1077      return projectId;
     1078    }
     1079   
     1080    /**
    10701081      Get the ID of the unit this annotation is using.
    10711082    */
  • trunk/src/core/net/sf/basedb/core/ItemContext.java

    r7982 r8083  
    172172  private Set<Integer> selected;
    173173  private boolean noPaging = false;
    174   private int rowsPerPage;
     174  private int rowsPerPage = 30;
    175175  private int page;
    176176  private int numFilterRows = 1;
    177177  private String sortProperty = null;
    178178  private String message;
     179  private Throwable exception;
    179180  private SortDirection sortDirection = SortDirection.ASC;
    180181  private Set<Include> include;
     
    255256    this.noPaging = true;
    256257    copyFromSyncFilter(syncFilter);
     258  }
     259 
     260  /**
     261    Initialize the context with the special purpose of configuring
     262    a query with the given filters. It should not be used for
     263    representing context in a GUI.
     264    @since 3.19.4
     265  */
     266  ItemContext(Item itemType, PropertyFilter... filters)
     267  {
     268    this.itemType = itemType;
     269    this.subContext = null;
     270    this.name = null;
     271    this.noPaging = true;
     272    for (PropertyFilter filter : filters)
     273    {
     274      setPropertyFilter(filter);
     275    }
    257276  }
    258277 
     
    623642  {
    624643    this.message = message;
     644  }
     645
     646  /**
     647    Get the exception that is the cause for the error in
     648    {@link #configureQuery(DbControl, EntityQuery, boolean)}.
     649    @since 3.19.14
     650  */
     651  public Throwable getException()
     652  {
     653    return exception;
     654  }
     655 
     656  /**
     657    Set the exception that is the cause for the error in
     658    {@link #configureQuery(DbControl, EntityQuery, boolean)}.
     659    @since 3.19.14
     660  */
     661  public void setException(Throwable t)
     662  {
     663    this.exception = t;
    625664  }
    626665 
     
    17521791           
    17531792            // Get default restriction
    1754             if (r == null) r = filter.getRestriction(dc, query);
     1793            if (r == null) r = filter.getRestriction(dc, query, context);
    17551794          }
    17561795          catch (Throwable ex)
     
    17591798            Application.getLogger().warn(msg, ex);
    17601799            setMessage(msg + ": " + ex.getMessage());
     1800            setException(ex);
    17611801          }
    17621802         
  • trunk/src/core/net/sf/basedb/core/PropertyFilter.java

    r8013 r8083  
    5050import net.sf.basedb.core.query.Hql;
    5151import net.sf.basedb.core.query.IdListRestriction;
     52import net.sf.basedb.util.extensions.ClientContext;
    5253import net.sf.basedb.util.listable.ListableUtil;
    5354import net.sf.basedb.util.listable.SourceItemTransformerFactory;
     
    560561  */
    561562  public Restriction getRestriction(DbControl dc, EntityQuery query)
     563  {
     564    return getRestriction(dc, query, null);
     565  }
     566 
     567  /**
     568    Create a restriction from the filter. There are a few special cases:
     569    <p>
     570    If the property starts with a $ it is interpreted as a <code>alias.property</code>
     571    value, ignoring the root alias of the query. The alias is removed from the
     572    property before proceeding.
     573    <p>
     574    If the property starts with a # or ## it is a filter on annotations. The rest of
     575    the property is the ID of the annotation type, and we use a
     576    {@link AnnotationSimpleRestriction} object for the restriction. If there
     577    is only one # in the prefix only direct annotations are searched, if there
     578    are two ##, inherited annotations are also searched.
     579    <p>
     580    If the property starts with a £, the rest of the property is the path that
     581    joins the {@link ReporterData#getReporterListScores()} collection. The filter
     582    value is the ID of the reporter list.
     583    <p>
     584    If the property starts with a @ the property is a collection of values and
     585    we always use an IN restriction, ignoring the specified operator.
     586    <p>
     587    If the property starts with &amp; it is a collection of entities. The property
     588    must end with a property name from the entity in the collection within paranthesis.
     589    Eg. &amp;experiments(name). The filter is applied as a subquery.
     590    <p>
     591    If the property starts with ! it is another special filter:
     592    <ul>
     593    <li>Since BASE 2.15: !sharedTo.name, which is used to filter
     594      shareable items by the name of users/groups/projects it has been shared to.
     595      The 'name' part can be replaced with any other property that is common for
     596      users, groups and projects (eg. description or id).
     597    <li>Since BASE 3.18: !x., which is intended to be handled by extensions. It
     598      is ignored by the BASE core code. The rest of the property should be a unique
     599      identifier for the extension that is handling the filter.
     600    </ul>
     601    <p>
     602    The property may also start with $@.
     603    @return The restriction
     604    @since 3.19.4
     605  */
     606  public Restriction getRestriction(DbControl dc, EntityQuery query, ClientContext context)
    562607  {
    563608   
     
    812857
    813858        PropertyFilter pp = new PropertyFilter(linkProperty, operator, getValue(), getValueType());
    814         subquery.restrict(pp.getRestriction(dc, subquery));
     859        subquery.restrict(pp.getRestriction(dc, subquery, context));
    815860        restriction = AnyToAnyRestriction.exists(null, linkName, linkType, subquery);
    816861      }
     
    851896      }
    852897      PropertyFilter pp = new PropertyFilter(targetProperty, operator, getValue(), getValueType());
    853       subquery.restrict(pp.getRestriction(dc, subquery));
     898      ClientContext subContext = new ClientContext(dc);
     899      if (context != null) subContext.linkAttributes(context);
     900      ItemContext targetItemContext = new ItemContext(targetType, pp);
     901      targetItemContext.configureQuery(dc, subquery, subContext, true);
     902      if (context != null && targetItemContext.getException() != null)
     903      {
     904        // Copy error messages
     905        ItemContext cc = context.getCurrentItem();
     906        cc.setMessage(targetItemContext.getMessage());
     907        cc.setException(targetItemContext.getException());
     908      }
    854909
    855910      // We then transform the list if ID:s to a list of child item ID:s using ItemList functionality
     
    9701025        PropertyFilter pp = new PropertyFilter("$" + thisAlias + "." + collectionFilter,
    9711026            actualOperator, actualValue, getValueType());
    972         subquery.restrict(pp.getRestriction(dc, query));
     1027        subquery.restrict(pp.getRestriction(dc, query, context));
    9731028       
    9741029        // Create the main restriction
     
    10751130        PropertyFilter pp = new PropertyFilter("$" + thisAlias + "." + collectionFilter,
    10761131            actualOperator, actualValue, getValueType());
    1077         subquery.restrict(pp.getRestriction(dc, query));
     1132        subquery.restrict(pp.getRestriction(dc, query, context));
    10781133       
    10791134        // Create the main restriction
     
    11231178        {
    11241179          users = new PropertyFilter("$usr." + filterProperty,
    1125               filterOp, filterValue, valueType).getRestriction(dc, query);
     1180              filterOp, filterValue, valueType).getRestriction(dc, query, context);
    11261181          groups = new PropertyFilter("$grp." + filterProperty,
    1127               filterOp, filterValue, valueType).getRestriction(dc, query);
     1182              filterOp, filterValue, valueType).getRestriction(dc, query, context);
    11281183          projects = new PropertyFilter("$prj." + filterProperty,
    1129               filterOp, filterValue, valueType).getRestriction(dc, query);
     1184              filterOp, filterValue, valueType).getRestriction(dc, query, context);
    11301185        }
    11311186        restriction = Hql.sharedTo(sharedTo, users, groups, projects);
  • trunk/src/core/net/sf/basedb/core/SyncFilter.java

    r7883 r8083  
    2828import java.util.Collections;
    2929import java.util.Comparator;
     30import java.util.HashSet;
    3031import java.util.List;
    3132import java.util.Map;
     33import java.util.Set;
    3234import java.util.SortedSet;
    3335import java.util.TreeSet;
     
    305307    }
    306308    context.copyFromSyncFilter(getData());
     309   
     310    // Make sure that all filtered columns are visible
     311    String columns = StringUtil.coalesce(context.getSetting("columns"), "");
     312    Set<String> visible = new HashSet<>(Arrays.asList(columns.split(",")));
     313    for (String filterProperty : getData().getPropertyFilters().keySet())
     314    {
     315      if (visible.contains(filterProperty)) continue;
     316     
     317      // Try some other variants when the filter is on a related-item column
     318      if (filterProperty.startsWith("/"))
     319      {
     320        if (filterProperty.endsWith("/name"))
     321        {
     322          // replace "name" with "." to use the entity instead
     323          filterProperty = filterProperty.replace("/name", "/.");
     324        }
     325        else if (filterProperty.endsWith(".name"))
     326        {
     327          // replace ".name" with "" to use the entity instead
     328          filterProperty = filterProperty.replace(".name", "");
     329        }
     330        else if (filterProperty.contains("/!x."))
     331        {
     332          // This is an extension filter convert to /!#
     333          filterProperty = filterProperty.replace("/!x.", "/!#");
     334        }
     335      }
     336      else if (filterProperty.startsWith("!x."))
     337      {
     338        filterProperty = filterProperty.substring(3);
     339      }
     340      if (!visible.contains(filterProperty))
     341      {
     342        columns+=","+filterProperty;
     343      }
     344    }
     345    context.setSetting("columns", columns);   
    307346  }
    308347
  • trunk/src/core/net/sf/basedb/core/log/TransactionDetails.java

    r7950 r8083  
    2323
    2424import java.util.Date;
     25import java.util.HashMap;
     26import java.util.Map;
    2527
    2628import org.hibernate.query.Query;
     
    5254  private String pluginName;
    5355  private String jobName;
     56  private Map<Integer, String> projectNames;
    5457 
    5558  /**
     
    179182
    180183  /**
     184    Get the name of the project with the given id.
     185    Special cases: 0 = default
     186  */
     187  public String getProjectName(int projectId)
     188  {
     189    String name = null;
     190    if (projectId > 0)
     191    {
     192      if (projectNames == null)
     193      {
     194        projectNames = new HashMap<>();
     195      }
     196      else
     197      {
     198        name = projectNames.get(projectId);
     199      }
     200      if (name == null)
     201      {
     202        Query<String> query = logControl.createHqlQuery("select prj.name from ProjectData prj where prj.id = " + projectId, String.class);
     203        name = query.uniqueResult();
     204        projectNames.put(projectId, name);
     205      }
     206    }
     207    else
     208    {
     209      name = "default";
     210    }
     211    return name;
     212  }
     213 
     214  /**
    181215    Utility method for loading the name of the currently executing plugin.
    182216    The first call to this method will lookup the name in the database
  • trunk/src/core/net/sf/basedb/core/log/db/AnnotationLogger.java

    r7381 r8083  
    8383    Integer parentId = null;
    8484    Integer parentType = null;
     85    Integer projectId = null;
    8586    Annotation.Source source = null;
    8687    List<?> newValues = null;
     
    105106        newValues = info.getNewValues();
    106107      }
     108      if (at.getProjectAnnotations()) projectId = info.getProjectId();
    107109    }
    108110    else if (entity instanceof AnnotationData)
     
    120122        valueType = Type.fromValue((Integer)tmp[1]);
    121123        if ((Boolean)tmp[2]) logOldPropertyValues = false;
     124        if ((Boolean)tmp[3]) projectId = annotation.getProjectId();
    122125      }
    123126      else
     
    125128        annotationType = at.getName();
    126129        valueType = Type.fromValue(at.getValueType());
     130        if (at.getProjectAnnotations()) projectId = annotation.getProjectId();
    127131        if (at.getDisableLogOfValues()) logOldPropertyValues = false;
    128132      }
     
    190194    change.setItemType(parentType);
    191195
     196    String projectInfo = "";
     197    if (projectId != null)
     198    {
     199      String projectName = logControl.getTransactionDetails().getProjectName(projectId);
     200      if (projectName != null) projectInfo = " ("+projectName+")";
     201    }
    192202    if (source == Annotation.Source.CLONED)
    193203    {
    194       change.setChangeInfo("ClonedAnnotation["+annotationType+"]");
     204      change.setChangeInfo("ClonedAnnotation["+annotationType+projectInfo+"]");
    195205    }
    196206    else if (source == Annotation.Source.INHERITED)
    197207    {
    198       change.setChangeInfo("InheritedAnnotation["+annotationType+"]");
     208      change.setChangeInfo("InheritedAnnotation["+annotationType+projectInfo+"]");
    199209    }
    200210    else
    201211    {
    202       change.setChangeInfo("Annotation["+annotationType+"]");
     212      change.setChangeInfo("Annotation["+annotationType+projectInfo+"]");
    203213    }
    204214    if (logOldPropertyValues)
  • trunk/src/core/net/sf/basedb/core/query/AnyToAnyRestriction.java

    r7813 r8083  
    77import net.sf.basedb.core.Item;
    88import net.sf.basedb.core.Type;
    9 import net.sf.basedb.core.query.Restriction;
    109import net.sf.basedb.util.EqualsHelper;
    1110
  • trunk/src/core/net/sf/basedb/core/query/ReporterListExpression.java

    r7813 r8083  
    2929import net.sf.basedb.core.RealTable;
    3030import net.sf.basedb.core.ReporterList;
    31 import net.sf.basedb.core.query.Expression;
    3231import net.sf.basedb.util.EqualsHelper;
    3332
  • trunk/src/core/net/sf/basedb/core/snapshot/AnnotationSnapshot.java

    r7703 r8083  
    3535import net.sf.basedb.core.DbControl;
    3636import net.sf.basedb.core.Item;
     37import net.sf.basedb.core.ItemNotFoundException;
    3738import net.sf.basedb.core.Permission;
     39import net.sf.basedb.core.PermissionDeniedException;
     40import net.sf.basedb.core.Project;
    3841import net.sf.basedb.core.Type;
    3942import net.sf.basedb.core.Unit;
    4043import net.sf.basedb.core.data.AnnotationData;
     44import net.sf.basedb.util.EqualsHelper;
    4145import net.sf.basedb.util.filter.Filter;
    4246import net.sf.basedb.util.units.UnitConverter;
     
    7074  {
    7175    return new AnnotationTypeComparator(dc);
     76  }
     77 
     78  /**
     79    Create a comparator for sorting annotation snapshots by the name
     80    of the project. Default values can be sorted first or last.
     81    @since 3.19.4
     82  */
     83  public static Comparator<AnnotationSnapshot> sortByProject(DbControl dc, boolean defaultFirst)
     84  {
     85    return new ProjectComparator(dc, defaultFirst);
    7286  }
    7387 
     
    308322 
    309323  /**
     324    Get the project that a project-specific annotation belongs to. Returns
     325    null if the annotation is a default value or if the project no longer
     326    exists.
     327    @throws PermissionDeniedException If the logged in user doesn't have access to the project
     328    @since 3.19.4
     329  */
     330  public Project getProject(DbControl dc)
     331  {
     332    try
     333    {
     334      return projectId == 0 ? null : Project.getById(dc, projectId);
     335    }
     336    catch (ItemNotFoundException ex)
     337    {} // The project may have been deleted
     338    return null;
     339  }
     340 
     341  /**
     342    Get the name of project if the annotation is a project-specific value.
     343  */
     344  String getProjectName(DbControl dc)
     345  {
     346    try
     347    {
     348      return projectId == 0 ? null : Project.getById(dc, projectId).getName();
     349    }
     350    catch (RuntimeException ex)
     351    {} // The project may have been deleted or the logged in user may not have permission
     352    return null;
     353  }
     354 
     355  /**
    310356    Get the id of the default annotation that this project-specific
    311357    annotation is overriding.
     
    587633    }
    588634  }
     635 
     636  /**
     637    Comparator implementation for sorting snapshots
     638    by name of project. If the project names are equal the snapshots are
     639    sorted by annotation type.
     640  */
     641  static class ProjectComparator
     642    implements Comparator<AnnotationSnapshot>
     643  {
     644    private final DbControl dc;
     645    private final boolean defaultFirst;
     646    private final AnnotationTypeComparator atComp;
     647   
     648    ProjectComparator(DbControl dc, boolean defaultFirst)
     649    {
     650      this.dc = dc;
     651      this.defaultFirst = defaultFirst;
     652      this.atComp = new AnnotationTypeComparator(dc);
     653    }
     654 
     655    @Override
     656    public int compare(AnnotationSnapshot o1, AnnotationSnapshot o2)
     657    {
     658      String n1 = o1.getProjectName(dc);
     659      String n2 = o2.getProjectName(dc);
     660      if (EqualsHelper.equals(n1, n2))
     661      {
     662        return atComp.compare(o1, o2);
     663      }
     664      else if (n1 == null)
     665      {
     666        return defaultFirst ? -1 : 1;
     667      }
     668      else if (n2 == null)
     669      {
     670        return defaultFirst ? 1 : -1;
     671      }
     672      return n1.compareTo(n2);
     673    }
     674  }
     675
    589676}
  • trunk/src/core/net/sf/basedb/core/snapshot/SnapshotManager.java

    r7767 r8083  
    129129 
    130130  private Integer projectId;
     131  private boolean allProjects;
    131132 
    132133  /**
     
    140141   
    141142    Calling this method with a null parameter IS NOT the same
    142     as calling {@link #setNoProject()}.
     143    as calling {@link #setNoProject()} or {@link #setAllProjects()}.
    143144   
    144145    @param project The project or null to only search for default
     
    151152  {
    152153    this.projectId = project == null ? 0 : project.getId();
     154    this.allProjects = false;
    153155  }
    154156 
     
    161163  {
    162164    this.projectId = null;
    163   }
     165    this.allProjects = false;
     166  }
     167 
     168  /**
     169    Search for project-specific annotations for all projects including the default
     170    values.
     171   
     172    @since 3.19.4
     173  */
     174  public void setAllProjects()
     175  {
     176    this.projectId = null;
     177    this.allProjects = true;
     178  }
    164179 
    165180  /**
     
    243258    {
    244259      int shotProjectId = shot.getProjectId();
    245       // Ignore all annotations not belonging to the specified project
    246       if (projectId != null && shotProjectId != projectId) continue;
    247       // Ignore project-specfic annotations not belonging to the current project
    248       if (shotProjectId != 0 && shotProjectId != activeProjectId) continue;
    249      
    250       // Ignore default annotations that already have a project-specific value
    251       // NOTE! Depends on correct sort order in snapshots
    252       if (overridden.contains(shot.getThisAnnotationId())) continue;
     260      if (!allProjects)
     261      {
     262        // Ignore all annotations not belonging to the specified project
     263        if (projectId != null && shotProjectId != projectId) continue;
     264        // Ignore project-specfic annotations not belonging to the current project
     265        if (shotProjectId != 0 && shotProjectId != activeProjectId) continue;
     266
     267        // Ignore default annotations that already have a project-specific value
     268        // NOTE! Depends on correct sort order in snapshots
     269        if (overridden.contains(shot.getThisAnnotationId())) continue;
     270      }
    253271     
    254272      if (shot.getSource() == Annotation.Source.PRIMARY)
     
    262280          result.add(shot);
    263281          // This is a project-specific annotation overriding a default value
    264           if (shotProjectId != 0) overridden.add(shot.getOverrideId());
     282          if (!allProjects && shotProjectId != 0) overridden.add(shot.getOverrideId());
    265283        }
    266284      }
  • trunk/src/core/net/sf/basedb/util/extensions/ClientContext.java

    r8045 r8083  
    2626
    2727import net.sf.basedb.core.DbControl;
     28import net.sf.basedb.core.ItemContext;
    2829import net.sf.basedb.core.SessionControl;
    2930
     
    232233  }
    233234 
     235  /**
     236    Link this context to the other context with respect to attributes.
     237    Existing attributes in this context will be copied to the other context.
     238    After this method call they will share the same internal storage so
     239    that updates to one context is reflected in the other.
     240    @since 3.19.4
     241  */
     242  public void linkAttributes(ClientContext other)
     243  {
     244    if (other.attributes == null) other.attributes = new HashMap<>();
     245    if (attributes != null) other.attributes.putAll(attributes);
     246    attributes = other.attributes;
     247  }
     248 
     249  /**
     250    Store all attributes that has been set on this context to
     251    the given ItemContext instance. Since the ItemContext is
     252    persistant for session it is possible to load the same
     253    attributes into another context later with {@link
     254    #loadAttributes(ItemContext, String)}
     255    @since 3.19.4
     256  */
     257  public void storeAttributes(ItemContext cc, String key)
     258  {
     259    if (attributes != null && attributes.size() > 0)
     260    {
     261      cc.setObject(key, attributes);
     262    }
     263  }
     264 
     265  /**
     266    Load attributes to this context from the given ItemContext
     267    instance.
     268    @since 3.19.4
     269  */
     270  public void loadAttributes(ItemContext cc, String key)
     271  {
     272    Map<String, Object> tmp = cc.getObject(key);
     273    if (tmp != null && tmp.size() > 0)
     274    {
     275      if (attributes != null)
     276      {
     277        attributes.putAll(tmp);
     278      }
     279      else
     280      {
     281        attributes = tmp;
     282      }
     283    }
     284  }
     285 
    234286}
  • trunk/src/core/net/sf/basedb/util/extensions/ExtensionsInvoker.java

    r7275 r8083  
    5858{
    5959
     60  private final ClientContext clientContext;
    6061  private final Collection<ExtensionContext<A>> contexts;
    6162  private boolean clearErrors;
     
    6465    Creates a new invoker object.
    6566  */
    66   ExtensionsInvoker(Collection<ExtensionContext<A>> contexts)
    67   {
     67  ExtensionsInvoker(ClientContext clientContext, Collection<ExtensionContext<A>> contexts)
     68  {
     69    this.clientContext = clientContext;
    6870    this.contexts = contexts;
     71  }
     72 
     73  /**
     74    Get the client context that is used by this invoker.
     75    @since 3.19.4
     76  */
     77  public ClientContext getClientContext()
     78  {
     79    return clientContext;
    6980  }
    7081 
  • trunk/src/core/net/sf/basedb/util/extensions/Registry.java

    r8045 r8083  
    715715    // Sort the extensions
    716716    filter.sort(contexts);
    717     return new ExtensionsInvoker<A>(contexts);
     717    return new ExtensionsInvoker<A>(clientContext, contexts);
    718718  }
    719719 
  • trunk/src/core/net/sf/basedb/util/formatter/PropertyFilterFormatter.java

    r7889 r8083  
    8787    StringBuilder sb = new StringBuilder();
    8888   
    89     if (property.startsWith("/"))
     89    int numHops = 0;
     90    while (property.startsWith("/") && numHops < 2)
    9091    {
    9192      // Parent or child item filter
     93      numHops++;
    9294      String[] tmp = property.split("/", 5);
    9395      int baseIndex = 0;
     
    122124      if (!property.startsWith("#")) sb.append(".");
    123125    }
    124     else if (property.startsWith("|"))
     126    if (property.startsWith("|"))
    125127    {
    126128      // Linked item filter
     
    262264      else
    263265      {
    264         if (property.startsWith("&") || property.startsWith("@") || property.startsWith("!"))
     266        if (property.startsWith("&") || property.startsWith("@"))
    265267        {
    266268          property = property.substring(1);
     269        }
     270        else if (property.startsWith("!x."))
     271        {
     272          property = property.substring(3);
    267273        }
    268274        sb.append(name(property)).append(" ").append(operator(operator.toString())).append(" ");
  • trunk/src/core/net/sf/basedb/util/jep/LeftFunction.java

    r6875 r8083  
    2626import org.nfunk.jep.ParseException;
    2727
    28 import net.sf.basedb.util.jep.JepFunction;
    2928
    3029/**
  • trunk/src/test/TestFile.java

    r7982 r8083  
    7979    int baseTracServer = TestFileServer.test_create("base.thep.lu.se", "BASE trac site",
    8080        "base", "base", "data/base.thep.lu.se.crt", null, null);
    81     int base2DemoServer = TestFileServer.test_create("base.onk.lu.se", "BASE demo site", null, null,
    82         "data/base.onk.lu.se.crt", null, null);
    8381    int extId1 = test_create("https://base.thep.lu.se/robots.txt", false, false);
    84     int extId2 = test_create("https://base.onk.lu.se/onk/images/baselogo.png", false, false);
    8582    int extId3 = test_create("https://base.thep.lu.se/login", false, false);
    8683    test_load(id1);
     
    9693    test_download(id1, "data/test.upload.txt");
    9794    test_download(extId1, "data/test.download.robots.txt");
    98     test_download(extId2, "data/test.download.baselogo.png");
    9995    test_download(extId3, null);
    10096   
     
    106102    test_delete(id2);
    107103    test_delete(extId1);
    108     test_delete(extId2);
    109104    test_delete(extId3);
    110105    TestFileServer.test_delete(baseTracServer);
    111     TestFileServer.test_delete(base2DemoServer);
    112106   
    113107    write("++Testing file "+(ok ? "OK" : "Failed")+"\n");
  • trunk/src/test/TestFileServer.java

    r7982 r8083  
    5757    // Standard tests: create, load, list
    5858    int id = test_create("base.thep.lu.se", "BASE trac site", "base", "base", "data/base.thep.lu.se.crt", null, null);
    59     int id2 = test_create("base.onk.lu.se", "BASE demo site", null, null, "data/base.onk.lu.se.crt", null, null);
    6059    test_load(id);
    6160    test_list(-1);
     
    6463    // Standard test: Delete
    6564    test_delete(id);
    66     test_delete(id2);
    6765    write("++Testing file servers "+(ok ? "OK" : "Failed")+"\n");
    6866    return ok;
  • trunk/src/test/TestItemImporter.java

    r7166 r8083  
    8989    int childScanId = TestItemSubtype.test_create(Item.DERIVEDBIOASSAY, "ChildScan", SystemItems.getId(DerivedBioAssay.SCAN));
    9090    int allSamplesSubtypeId = TestItemSubtype.test_create(Item.SAMPLE, "All samples");
    91     int base2DemoServer = TestFileServer.test_create("base.onk.lu.se", "BASE demo site", null, null,
    92         "data/base.onk.lu.se.crt", null, null);
    9391
    9492    TestProject.test_defaultItem(projectId, Item.PROTOCOL, defaultSamplingProtocolId);
     
    105103    ok = ok & TestFile.test_list("/file-import", 1);  // 'reporters.txt'
    106104    ok = ok & TestFile.test_list("/file-import/raw", 2);  // 'file1.txt' and 'file2.txt'
    107     ok = ok & TestFile.test_list("/file-import/ext", 2);  // 'robots.txt' and 'baselogo.png'
     105    ok = ok & TestFile.test_list("/file-import/ext", 1);  // 'robots.txt'
    108106   
    109107    // Import annotation types
     
    305303    TestItemSubtype.test_delete(childScanId);
    306304    TestItemSubtype.test_delete(allSamplesSubtypeId);
    307     TestFileServer.test_delete(base2DemoServer);
    308305    TestProject.test_delete(projectId);
    309306
  • trunk/src/test/data/test.batchimport.files.txt

    r6576 r8083  
    33raw/file1.txt     Raw data    Raw data #1
    44raw/file2.txt     Raw data    Raw data #2
    5 /file-import/ext/robots.txt http://base.thep.lu.se/robots.txt     text/plain  Robots file for BASE
    6 /file-import/ext/baselogo.png https://base.onk.lu.se/onk/images/baselogo.png  base.onk.lu.se   
     5/file-import/ext/robots.txt https://base.thep.lu.se/robots.txt      text/plain  Robots file for BASE
  • trunk/www/admin/hardware/list_hardware.jsp

    r8026 r8083  
    514514                          list="<%=loader.getValues()%>"
    515515                          suffix="<%=loader.getUnitSymbol()%>"
    516                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     516                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    517517                        /><%
    518518                      }
  • trunk/www/admin/protocols/list_protocol.jsp

    r8026 r8083  
    530530                          list="<%=loader.getValues()%>"
    531531                          suffix="<%=loader.getUnitSymbol()%>"
    532                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     532                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    533533                        /><%
    534534                      }
  • trunk/www/admin/software/list_software.jsp

    r8026 r8083  
    514514                          list="<%=loader.getValues()%>"
    515515                          suffix="<%=loader.getUnitSymbol()%>"
    516                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     516                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    517517                        /><%
    518518                      }
  • trunk/www/biomaterials/bioplates/list_bioplates.jsp

    r7982 r8083  
    668668                          list="<%=loader.getValues()%>"
    669669                          suffix="<%=loader.getUnitSymbol()%>"
    670                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     670                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    671671                        /><%
    672672                      }
  • trunk/www/biomaterials/bioplates/wells/list_biowells.jsp

    r7982 r8083  
    650650                          list="<%=loader.getValues()%>"
    651651                          suffix="<%=loader.getUnitSymbol()%>"
    652                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     652                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    653653                        /><%
    654654                      }
  • trunk/www/biomaterials/biosources/list_biosources.jsp

    r7982 r8083  
    581581                          list="<%=loader.getValues()%>"
    582582                          suffix="<%=loader.getUnitSymbol()%>"
    583                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     583                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    584584                        /><%
    585585                      }
  • trunk/www/biomaterials/extracts/list_extracts.jsp

    r7982 r8083  
    10251025                          list="<%=loader.getValues()%>"
    10261026                          suffix="<%=loader.getUnitSymbol()%>"
    1027                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     1027                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    10281028                        /><%
    10291029                      }
     
    10461046                          list="<%=loader.getValues()%>"
    10471047                          suffix="<%=loader.getUnitSymbol()%>"
    1048                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     1048                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    10491049                        /><%
    10501050                      }
  • trunk/www/biomaterials/kits/list_kits.jsp

    r7982 r8083  
    515515                          list="<%=loader.getValues()%>"
    516516                          suffix="<%=loader.getUnitSymbol()%>"
    517                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     517                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    518518                        /><%
    519519                      }
  • trunk/www/biomaterials/samples/list_samples.jsp

    r7982 r8083  
    908908                          list="<%=loader.getValues()%>"
    909909                          suffix="<%=loader.getUnitSymbol()%>"
    910                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     910                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    911911                        /><%
    912912                      }
  • trunk/www/biomaterials/tags/list_tags.jsp

    r7982 r8083  
    507507                          list="<%=loader.getValues()%>"
    508508                          suffix="<%=loader.getUnitSymbol()%>"
    509                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     509                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    510510                        /><%
    511511                      }
  • trunk/www/common/annotations/annotate.js

    r7926 r8083  
    321321        var msg = '';
    322322        var defaultValues = '';
     323        var projectId = Data.int('page-data', 'project-id');
     324        var activeProjectId = Data.int('page-data', 'active-project-id');
    323325        if (ann.projectId != 0)
    324326        {
     
    337339        else
    338340        {
    339           msg = 'This is the default value for a project annotation.<br>Changing it will create a project-specific value.';
     341          msg = 'This is the default value for a project annotation.';
     342          if (projectId < 0 && activeProjectId > 0) msg += '<br>Changing it will create a project-specific value.';
    340343        }
    341344        Doc.element('project-annotations-message').innerHTML = msg;
  • trunk/www/common/annotations/annotate.jsp

    r7954 r8083  
    3535  import="net.sf.basedb.core.Nameable"
    3636  import="net.sf.basedb.core.Protocol"
     37  import="net.sf.basedb.core.Project"
    3738  import="net.sf.basedb.core.Subtypable"
    3839  import="net.sf.basedb.core.ItemSubtype"
     
    8586final Item itemType = Item.valueOf(request.getParameter("item_type"));
    8687final int itemId = Values.getInt(request.getParameter("item_id"));
     88final int projectId = Values.getInt(request.getParameter("project_id"), -1);
    8789final int protocolId = Values.getInt(request.getParameter("protocol_id"), -1);
    8890final int subtypeId = Values.getInt(request.getParameter("subtype_id"), -1);
     
    139141  }
    140142 
     143  // Load a project if it has been specified
     144  Project project = null;
     145  boolean readProject = true;
     146  try
     147  {
     148    if (projectId > 0) project = Project.getById(dc, projectId);
     149  }
     150  catch (PermissionDeniedException ex)
     151  {
     152    readProject = false;
     153  }
     154 
    141155  // Load the current subtype
    142156  // NOTE! User may have selected a different subtype in the form than what is
     
    196210  final ItemQuery<AnnotationType> parameterQuery = Base.getProtocolParametersQuery(protocol);
    197211
     212  if (projectId >= 0)
     213  {
     214    title += " (" + (project != null ? "for project: "+HTML.encodeTags(project.getName()) : "default values") + ")";
     215    annotationTypeQuery.restrict(Restrictions.eq(Hql.property("projectAnnotations"), Expressions.bool(true)));
     216    if (parameterQuery != null)
     217    {
     218      parameterQuery.restrict(Restrictions.eq(Hql.property("projectAnnotations"), Expressions.bool(true)));
     219    }
     220  }
     221 
    198222  // Holds all categories that we have found
    199223  final Set<AnnotationTypeCategory> allCategories =
     
    230254    // Load the existing annotations
    231255    as = item.getAnnotationSet();
    232     ItemQuery<Annotation> aQuery = as.getAnnotations(null);
     256    ItemQuery<Annotation> aQuery = projectId < 0 ? as.getAnnotations(null) : as.getProjectAnnotations(null, project);
    233257    aQuery.order(Orders.asc(Hql.property("annotationType.name")));
    234258    List<Annotation> annotations = aQuery.list(dc);
     
    543567      data-annotation-type-id="<%=annotationTypeId%>"
    544568      data-annotation-id="<%=annotationId %>"
     569      data-project-id="<%=projectId%>"
     570      data-active-project-id="<%=sc.getActiveProjectId()%>"
    545571      data-date-format="<%=htmlDateFormat%>"
    546572      data-datetime-format="<%=htmlDateTimeFormat%>"
     
    846872        <input type="hidden" name="item_type" value="<%=itemType.name()%>">
    847873        <input type="hidden" name="item_id" value="<%=itemId%>">
     874        <%
     875        if (projectId >= 0)
     876        {
     877          %>
     878          <input type="hidden" name="project_id" value="<%=projectId%>">
     879          <%
     880        }
     881        %>
    848882      </form>
    849883      <%
  • trunk/www/common/annotations/list.js

    r7604 r8083  
    8282  {
    8383    var target = event.currentTarget;
    84     var annotationId = Data.int(target, 'annotation');
    8584    var url =  App.getRoot() + 'common/annotations/annotate.jsp?ID='+App.getSessionId();
    86     url += '&annotation_id='+Data.int(target, 'annotation');;
     85    url += '&annotation_id='+Data.int(target, 'annotation');
    8786    url += '&item_type='+Data.get(target, 'item-type');
    8887    url += '&item_id='+Data.get(target, 'item-id');
    8988    url += '&annotationtype_id='+Data.get(target, 'annotation-type');
     89    var projectId = Data.get(target, 'project');
     90    if (projectId) url += '&project_id='+projectId;
    9091    url += '&standalone=1';
    9192    Dialogs.openPopup(url, 'EditAnnotation', 750, 500);
  • trunk/www/common/annotations/list_annotations.jsp

    r7954 r8083  
    3030  import="net.sf.basedb.core.AnnotationType"
    3131  import="net.sf.basedb.core.Protocol"
     32  import="net.sf.basedb.core.Project"
    3233  import="net.sf.basedb.core.AnnotationSet"
    3334  import="net.sf.basedb.core.Annotation"
     
    5354  import="net.sf.basedb.core.snapshot.AnnotationSnapshot"
    5455  import="net.sf.basedb.core.snapshot.SnapshotManager"
     56  import="net.sf.basedb.core.snapshot.ProjectFilter"
     57  import="net.sf.basedb.core.snapshot.AnnotationTypeFilter"
    5558  import="net.sf.basedb.clients.web.Base"
    5659  import="net.sf.basedb.clients.web.util.HTML"
     
    6164  import="net.sf.basedb.clients.web.formatter.FormatterFactory"
    6265  import="net.sf.basedb.util.Values"
     66  import="net.sf.basedb.util.filter.Filter"
     67  import="net.sf.basedb.util.filter.AllOfFilter"
     68  import="java.util.Arrays"
    6369  import="java.util.Collections"
    6470  import="java.util.ArrayList"
     
    8591Set<AnnotationType> protocolParameters = null;
    8692Map<AnnotationType, AnnotationSnapshot> existing = null;
     93
     94boolean hasProjectSpecificAnnotationType = false;
     95AnnotationTypeFilter annotationTypeFilter = null;
     96Filter<AnnotationSnapshot> projectFilter = null;
    8797
    8898try
     
    124134      {
    125135        existing.put(a.getAnnotationType(dc), a);
     136        hasProjectSpecificAnnotationType |= at.getProjectAnnotations();
    126137      }
    127138      else
     
    132143    Collections.sort(inheritedAnnotations, AnnotationSnapshot.sortByAnnotationType(dc));
    133144    annotationTypes.addAll(existing.keySet());
     145  }
     146 
     147  if (hasProjectSpecificAnnotationType)
     148  {
     149    manager.setAllProjects();
     150    annotationTypeFilter = new AnnotationTypeFilter();
     151    projectFilter = new AllOfFilter<>(Arrays.asList(annotationTypeFilter, new ProjectFilter(dc)));
    134152  }
    135153 
     
    151169    annotationTypes.removeAll(protocolParameters);
    152170  }
     171 
    153172  // Load the current subtype
    154173  // NOTE! User may have selected a different subtype in the form than what is
     
    187206  }
    188207  Formatter<Date> dateTimeFormatter = FormatterFactory.getDateTimeFormatter(sc);
     208  String projectValuesTitle = "<div class=\"project-values\"><div>Project values</div><div></div><div></div><div class=\"last-modified\">Last modified</div></div>";
    189209  %>
    190210  <base:page type="iframe" id="list-annotations">
     
    207227    {
    208228      float: right;
     229    }
     230   
     231    .project-values
     232    {
     233      display: grid;
     234      grid-template-columns: 1fr 1fr 18px 12em;
     235    }
     236   
     237    .project-values div
     238    {
     239      padding: 1px 2px;
     240      border-bottom-width: 1px;
     241      border-bottom-style: dotted;
     242    }
     243   
     244    /* Remove borders on last row */
     245    .project-values div:nth-last-child(-n+4)
     246    {
     247      border-bottom-width: 0;
     248    }
     249   
     250    .project-values .default-value
     251    {
     252      font-style: italic;
     253    }
     254   
     255    .project-values .current-value::before
     256    {
     257      font-weight: bold;
     258      content: '›';
     259      margin-right: 2px;
     260    }
     261
     262    .project-values .other-value::before
     263    {
     264      font-weight: bold;
     265      content: '›';
     266      color: transparent;
     267      margin-right: 2px;
     268    }
     269   
     270    /* Span accross first 2 columns */
     271    .project-values .no-default
     272    {
     273      font-style: italic;
     274      grid-column-start: 1;
     275      grid-column-end: 3;
     276    }
     277   
     278    .last-modified
     279    {
     280      white-space: nowrap;
     281      border-left-width: 1px;
     282      border-left-style: dotted;
    209283    }
    210284  </style>
     
    249323          <tbl:columndef
    250324            id="values"
    251             title="Values"
     325            title="<%=hasProjectSpecificAnnotationType ? "Current values" : "Values" %>"
    252326          />
    253327          <tbl:columndef
    254             id="note"
    255             title="Note"
     328            id="projectValues"
     329            title="<%=projectValuesTitle%>"
     330            tooltip="Project values"
     331            show="<%=hasProjectSpecificAnnotationType ? "always" : "never" %>"
     332            style="padding: 0;"
    256333          />
    257334          <tbl:columndef
     
    259336            title="Last modified"
    260337            formatter="<%=dateTimeFormatter%>"
     338            show="<%=hasProjectSpecificAnnotationType ? "never" : "always" %>"
    261339          />
    262340          <tbl:columndef
     
    293371              AnnotationSnapshot a = existing != null ? existing.get(at) : null;
    294372              List<?> values = null;
    295               List<?> defaultValues = null;
    296373              Formatter<Object> formatter = null;
    297374              boolean projectSpecific = false;
     
    303380                values = a.getActualValues(ann.getUnitConverter(null), ann.getValueType());
    304381                formatter = FormatterFactory.getAnnotationFormatter(sc, ann, null);
    305                
    306382                projectSpecific = a.getProjectId() != 0;
    307                 if (ann.isOverridingDefault())
    308                 {
    309                   Annotation defAnn = ann.getDefaultAnnotation();
    310                   defaultValues = defAnn.getValues();
    311                 }
    312383              }
    313384              if (values != null || !at.isRemoved())
     
    337408                    visible="<%=at.isRemoved()%>"
    338409                    /><%=Base.getLinkedName(ID, at, false, true)%></tbl:cell>
    339                   <tbl:cell column="values" clazz="<%=projectSpecific && defaultValues != null ? "cell ps-annotation" : "cell" %>">
     410                  <tbl:cell column="values" clazz="<%=projectSpecific ? "cell ps-annotation" : "cell" %>" disableOverflowCheck="true">
    340411                    <%=values == null || values.size() == 0 ? "<i>- no values -</i>" : Values.getString(values, ", ", true, formatter)%>
    341                     <base:icon image="edit.png" subclass="edit-annotation auto-init link"
     412                    <base:icon image="<%=a==null?"edit_create.png":"edit.png" %>" subclass="edit-annotation auto-init link"
    342413                      data-auto-init="edit-annotation"
    343414                      data-annotation="<%=a!=null ? a.getThisAnnotationId() : 0 %>"
     
    345416                      data-item-type="<%=itemType.name()%>"
    346417                      data-item-id="<%=itemId %>"
    347                       tooltip="Modify the values of this annotation"
     418                      tooltip="<%=a==null?"Create this annotation":"Modify the values of this annotation"%>"
    348419                      visible="<%=annotatePermission %>"
    349420                    />
    350421                  </tbl:cell>
    351                   <tbl:cell column="note">
     422                  <tbl:cell column="projectValues" disableOverflowCheck="true" style="padding:0;">
     423                    <div class="project-values">
    352424                    <%
    353                     if (projectSpecific)
     425                    if (at.getProjectAnnotations())
     426                    {
     427                      annotationTypeFilter.setAnnotationType(at);
     428                      List<AnnotationSnapshot> projectValues = manager.findAnnotations(dc, snapshot, projectFilter, false);
     429                      projectValues.sort(AnnotationSnapshot.sortByProject(dc, true));
     430                      int numValues = projectValues.size();
     431                      if (numValues == 0 || projectValues.get(0).getProjectId() != 0)
     432                      {
     433                        %>
     434                        <div class="no-default"><%=numValues > 0 ? "No default value":""%></div>
     435                        <div><base:icon image="edit_create.png" subclass="edit-annotation auto-init link"
     436                            data-auto-init="edit-annotation"
     437                            data-annotation="0"
     438                            data-annotation-type="<%=at.getId()%>"
     439                            data-project="0"
     440                            data-item-type="<%=itemType.name()%>"
     441                            data-item-id="<%=itemId %>"
     442                            tooltip="Create a default value"
     443                            visible="<%=annotatePermission %>"
     444                          /></div>
     445                        <div class="last-modified"></div>
     446                        <%
     447                      }
     448                      if (numValues > 0)
     449                      {
     450                        for (AnnotationSnapshot pv : projectValues)
     451                        {
     452                          boolean isDefault = pv.getProjectId() == 0;
     453                          boolean isCurrent = pv == a;
     454                          Annotation ann = pv.getThisAnnotation(dc);
     455                          values = pv.getActualValues(ann.getUnitConverter(null), ann.getValueType());
     456                          formatter = FormatterFactory.getAnnotationFormatter(sc, ann, null);
     457                          if (numValues > 1)
     458                          {
     459                            %>
     460                            <div class="<%=isCurrent?"current-value":"other-value"%><%=isDefault ? " default-value":""%>"><%=isDefault ? "Default" : pv.getProject(dc).getName() %></div>
     461                            <div><%=Values.getString(values, ", ", true, formatter) %></div>
     462                            <%
     463                          }
     464                          else
     465                          {
     466                            %>
     467                            <div></div><div></div>
     468                            <%
     469                          }
     470                          %>
     471                          <div><base:icon image="edit.png" subclass="edit-annotation auto-init link"
     472                              data-auto-init="edit-annotation"
     473                              data-annotation="<%=pv.getThisAnnotationId()%>"
     474                              data-annotation-type="<%=at.getId()%>"
     475                              data-project="<%=pv.getProjectId()%>"
     476                              data-item-type="<%=itemType.name()%>"
     477                              data-item-id="<%=itemId %>"
     478                              tooltip="<%=isDefault ? "Modify the default value" : "Modify the value in this project"%>"
     479                              visible="<%=annotatePermission %>"
     480                            /></div>
     481                          <div class="last-modified"><%=dateTimeFormatter.format(pv.getThisLastUpdate())%></div>
     482                          <%
     483                        }
     484                      }
     485                    }
     486                    else if (a != null)
    354487                    {
    355488                      %>
    356                       <%=defaultValues == null || defaultValues.size() == 0 ? "No default value" : "Default value: " + Values.getString(defaultValues, ", ", true, formatter)%>
     489                      <div></div><div></div><div></div>
     490                      <div class="last-modified"><%=dateTimeFormatter.format(a.getThisLastUpdate())%></div>
    357491                      <%
    358492                    }
    359493                    %>
     494                    </div>
    360495                  </tbl:cell>
    361496                  <tbl:cell column="lastModified" value="<%=a == null ? null : a.getThisLastUpdate() %>" />
     
    411546            <tbl:columndef
    412547              id="values"
    413               title="Values"
     548              title="<%=hasProjectSpecificAnnotationType ? "Current values" : "Values" %>"
    414549            />
    415550            <tbl:columndef
    416               id="note"
    417               title="Note"
     551              id="projectValues"
     552              title="<%=projectValuesTitle%>"
     553              tooltip="Project values"
     554              show="<%=hasProjectSpecificAnnotationType ? "always" : "never" %>"
     555              style="padding: 0;"
    418556            />
    419557            <tbl:columndef
     
    421559              title="Last modified"
    422560              formatter="<%=dateTimeFormatter%>"
     561              show="<%=hasProjectSpecificAnnotationType ? "never" : "always" %>"
    423562            />
    424563            <tbl:columndef
     
    439578                boolean annotatePermission = writePermission & at.hasPermission(Permission.USE);
    440579                AnnotationSnapshot a = existing != null ? existing.get(at) : null;
     580                List<?> values = null;
    441581                Formatter<Object> formatter = null;
    442                 List<?> values = null;
    443                 List<?> defaultValues = null;
    444582                boolean projectSpecific = false;
    445583                if (a != null)
     
    450588                  values = a.getActualValues(ann.getUnitConverter(null), ann.getValueType());
    451589                  formatter = FormatterFactory.getAnnotationFormatter(sc, ann, null);
    452                  
    453590                  projectSpecific = a.getProjectId() != 0;
    454                   if (ann.isOverridingDefault())
    455                   {
    456                     Annotation defAnn = ann.getDefaultAnnotation();
    457                     defaultValues = defAnn.getValues();
    458                   }
    459591                }
    460592                if (values != null || !at.isRemoved())
     
    467599                      visible="<%=at.isRemoved()%>"
    468600                    /><%=Base.getLinkedName(ID, at, false, true)%></tbl:cell>
    469                     <tbl:cell column="values" clazz="<%=projectSpecific && defaultValues != null ? "cell ps-annotation" : "cell" %>">
     601                    <tbl:cell column="values" clazz="<%=projectSpecific ? "cell ps-annotation" : "cell" %>" disableOverflowCheck="true">
    470602                      <%=values == null || values.size() == 0 ? "<i>- no values -</i>" : Values.getString(values, ", ", true, formatter)%>
    471                       <base:icon image="edit.png" subclass="edit-annotation auto-init link"
     603                      <base:icon image="<%=a==null?"edit_create.png":"edit.png" %>" subclass="edit-annotation auto-init link"
    472604                        data-auto-init="edit-annotation"
    473605                        data-annotation="<%=a!=null ? a.getThisAnnotationId() : 0 %>"
     
    475607                        data-item-type="<%=itemType.name()%>"
    476608                        data-item-id="<%=itemId %>"
    477                         tooltip="Modify the values of this protocol parameter"
     609                        tooltip="<%=a==null?"Create this protocol parameter":"Modify the values of this protocol parameter"%>"
    478610                        visible="<%=annotatePermission %>"
    479611                      />
    480612                    </tbl:cell>
    481                     <tbl:cell column="note">
     613                    <tbl:cell column="projectValues" disableOverflowCheck="true" style="padding:0;">
     614                      <div class="project-values">
    482615                      <%
    483                       if (projectSpecific)
     616                      if (at.getProjectAnnotations())
     617                      {
     618                        annotationTypeFilter.setAnnotationType(at);
     619                        List<AnnotationSnapshot> projectValues = manager.findAnnotations(dc, snapshot, projectFilter, false);
     620                        projectValues.sort(AnnotationSnapshot.sortByProject(dc, true));
     621                        int numValues = projectValues.size();
     622                        if (numValues == 0 || projectValues.get(0).getProjectId() != 0)
     623                        {
     624                          %>
     625                          <div class="no-default"><%=numValues > 0 ? "No default value":""%></div>
     626                          <div><base:icon image="edit_create.png" subclass="edit-annotation auto-init link"
     627                              data-auto-init="edit-annotation"
     628                              data-annotation="0"
     629                              data-annotation-type="<%=at.getId()%>"
     630                              data-project="0"
     631                              data-item-type="<%=itemType.name()%>"
     632                              data-item-id="<%=itemId %>"
     633                              tooltip="Create a default value"
     634                              visible="<%=annotatePermission %>"
     635                            /></div>
     636                          <div class="last-modified"></div>
     637                          <%
     638                        }
     639                        if (numValues > 0)
     640                        {
     641                          for (AnnotationSnapshot pv : projectValues)
     642                          {
     643                            boolean isDefault = pv.getProjectId() == 0;
     644                            boolean isCurrent = pv == a;
     645                            Annotation ann = pv.getThisAnnotation(dc);
     646                            values = pv.getActualValues(ann.getUnitConverter(null), ann.getValueType());
     647                            formatter = FormatterFactory.getAnnotationFormatter(sc, ann, null);
     648                            if (numValues > 1)
     649                            {
     650                              %>
     651                              <div class="<%=isCurrent?"current-value":"other-value"%><%=isDefault ? " default-value":""%>"><%=isDefault ? "Default" : pv.getProject(dc).getName() %></div>
     652                              <div><%=Values.getString(values, ", ", true, formatter) %></div>
     653                              <%
     654                            }
     655                            else
     656                            {
     657                              %>
     658                              <div></div><div></div>
     659                              <%
     660                            }
     661                            %>
     662                            <div><base:icon image="edit.png" subclass="edit-annotation auto-init link"
     663                                data-auto-init="edit-annotation"
     664                                data-annotation="<%=pv.getThisAnnotationId()%>"
     665                                data-annotation-type="<%=at.getId()%>"
     666                                data-project="<%=pv.getProjectId()%>"
     667                                data-item-type="<%=itemType.name()%>"
     668                                data-item-id="<%=itemId %>"
     669                                tooltip="Modify the values of this protocol parameter"
     670                                visible="<%=annotatePermission %>"
     671                            /></div>
     672                            <div class="last-modified"><%=dateTimeFormatter.format(pv.getThisLastUpdate())%></div>
     673                            <%
     674                          }
     675                        }
     676                      }
     677                      else if (a != null)
    484678                      {
    485679                        %>
    486                         <%=defaultValues == null || defaultValues.size() == 0 ? "No default value" : "Default value: " + Values.getString(defaultValues, ", ", true, formatter)%>
     680                        <div></div><div></div><div></div>
     681                        <div class="last-modified"><%=dateTimeFormatter.format(a.getThisLastUpdate())%></div>
    487682                        <%
    488683                      }
    489684                      %>
     685                      </div>
    490686                    </tbl:cell>
    491687                    <tbl:cell column="lastModified" value="<%=a == null ? null : a.getThisLastUpdate() %>" />
     
    625821              <tbl:row>
    626822                <tbl:cell column="annotation"><%=Base.getLinkedName(ID, at, at == null, true)%></tbl:cell>
    627                 <tbl:cell column="values" clazz="<%=projectSpecific && defaultValues != null ? "cell ps-annotation" : "cell" %>">
     823                <tbl:cell column="values" clazz="<%=projectSpecific ? "cell ps-annotation" : "cell" %>" disableOverflowCheck="true">
    628824                  <%=values == null || values.size() == 0 ?
    629825                    "<i>- no values -</i>" : Values.getString(values, ", ", true, formatter)%>
  • trunk/www/common/columns/add_relateditem_column.js

    r7885 r8083  
    2828  var configure = {};
    2929 
    30   var currentSubtypes;
    31   var currentAnnotationTypesAll;
    32   var currentAnnotationTypesCategory;
     30  var currentTargetSubtypes;
     31  var currentTargetAnnotationTypesAll;
     32  var currentTargetAnnotationTypesCategory;
     33  var currentTargetExtensionColumns;
     34 
     35  var currentChildItemTypes;
     36  var currentChildSubtypes;
     37  var currentChildAnnotationTypesAll;
     38  var currentChildAnnotationTypesCategory;
     39  var currentChildExtensionColumns;
    3340 
    3441  // See also net.sf.basedb.clients.web.extensions.list.RelatedItemColumn.pathTitles
    35 
    3642  var nameCol = {'id': '.', 'text': 'Name'};
    3743  var externalIdCol = {'id': 'externalId', 'text': 'External ID'};
     
    6874  {
    6975    Events.addEventHandler('targetItemType', 'change', configure.targetItemTypeOnChange);
    70     Events.addEventHandler('subtype', 'change', configure.subtypeOnChange);
     76    Events.addEventHandler('targetSubtype', 'change', configure.targetSubtypeOnChange);
     77    Events.addEventHandler('childItemType', 'change', configure.childItemTypeOnChange);
     78    Events.addEventHandler('childSubtype', 'change', configure.childSubtypeOnChange);
    7179    Events.addEventHandler('showAllAnnotationTypes', 'change', configure.updateColumnsList);
    72     Events.addEventHandler('column', 'dblclick', configure.addColumn);
    7380    Events.addEventHandler(document.body, 'click', configure.hideMessage);
    7481   
     
    7885  }
    7986 
    80 
    81   configure.subtypeOnChange = function(event)
    82   {
    83     var frm = document.forms['relatedItems'];
     87  // Clear existing columns
     88  configure.clearColumns = function()
     89  {
     90    Doc.element('columns').innerHTML = '';
     91    Doc.element('annotations').innerHTML = '';
     92    Doc.element('extensions').innerHTML = '';
     93    Doc.hide('targetPath');
     94    Doc.hide('childPath');
     95  }
     96  // Clear the list and disable it
     97  configure.clearList = function(listOrId)
     98  {
     99    var list = Doc.element(listOrId);
     100    list.disabled = true;
     101    list.length = 1;
     102  }
     103 
     104  configure.targetItemTypeOnChange = function(event)
     105  {
     106    // Reset everything that depends on the target item type
     107    configure.clearColumns();
     108    configure.clearList('targetSubtype');
     109    configure.clearList('childItemType');
     110    configure.clearList('childSubtype');
     111   
     112    var frm = document.forms['relatedItems'];
     113    var sourceItemType = frm.item_type.value;
    84114    var targetItemType = frm.targetItemType.value;
    85     var category = frm.subtype[frm.subtype.selectedIndex].text;
     115    var direction = Data.get(frm.targetItemType[frm.targetItemType.selectedIndex], 'direction');
     116   
     117    if (targetItemType)
     118    {
     119      var url = 'ajax.jsp?ID='+App.getSessionId();
     120      url += '&cmd=GetSubtypesAndAnnotationTypes&itemType='+encodeURIComponent(targetItemType);
     121      url += '&sourceItemType='+encodeURIComponent(sourceItemType);
     122      url += '&direction='+encodeURIComponent(direction);
     123     
     124      var request = Ajax.getXmlHttpRequest();
     125      request.open("GET", url, true);
     126      request.send(null);
     127      Ajax.setReadyStateHandler(request, configure.targetTypeInfoLoaded, configure.targetTypeInfoLoaded);
     128    }
     129  }
     130 
     131  configure.targetSubtypeOnChange = function(event)
     132  {
     133    // Reset everything that depends on the target subtype
     134    configure.clearColumns();
     135   
     136    var frm = document.forms['relatedItems'];
     137    var targetItemType = frm.targetItemType.value;
     138    var sourceItemType = frm.item_type.value;
     139    var category = frm.targetSubtype.selectedIndex > 0 ? frm.targetSubtype[frm.targetSubtype.selectedIndex].text : null;
    86140    if (targetItemType && category)
    87141    {
    88142      var url = 'ajax.jsp?ID='+App.getSessionId();
    89143      url += '&cmd=GetAnnotationTypesForCategory&itemType='+encodeURIComponent(targetItemType);
     144      url += '&sourceItemType='+encodeURIComponent(sourceItemType);
    90145      url += '&category='+encodeURIComponent(category);
    91146     
     
    93148      request.open("GET", url, true);
    94149      request.send(null);
    95       Ajax.setReadyStateHandler(request, configure.typeInfoLoaded, configure.typeInfoLoaded);
    96     }
    97   }
    98  
    99   configure.targetItemTypeOnChange = function(event)
    100   {
    101     var targetItemType = event.currentTarget.value;
    102     if (targetItemType)
     150      Ajax.setReadyStateHandler(request, configure.targetTypeInfoLoaded, configure.targetTypeInfoLoaded);
     151    }
     152  }
     153 
     154  configure.targetTypeInfoLoaded = function(request)
     155  {
     156    var response = JSON.parse(request.responseText);
     157    if (response.status != 'ok')
     158    {
     159      App.debug(request.responseText);
     160      return;
     161    }
     162    var frm = document.forms['relatedItems'];
     163   
     164    if (response.childTypes)
     165    {
     166      frm.childItemType.length = 1;
     167      currentChildItemTypes = response.childTypes;
     168      for (var i = 0; i < currentChildItemTypes.length; i++)
     169      {
     170        var st = currentChildItemTypes[i];
     171        frm.childItemType[frm.childItemType.length] = new Option('Child: '+st.name, st.id);
     172      }
     173      frm.childItemType.selectedIndex = 0;
     174      frm.childItemType.disabled = currentChildItemTypes.length == 0;
     175    }
     176   
     177    if (response.subtypes)
     178    {
     179      currentTargetSubtypes = response.subtypes;
     180      frm.targetSubtype.length = 1;
     181      for (var i = 0; i < currentTargetSubtypes.length; i++)
     182      {
     183        var st = currentTargetSubtypes[i];
     184        frm.targetSubtype[frm.targetSubtype.length] = new Option(st.name, st.id);
     185      }
     186      frm.targetSubtype.selectedIndex = 0;
     187      frm.targetSubtype.disabled = currentTargetSubtypes.length == 0;
     188    }
     189   
     190    if (response.annotationTypes) currentTargetAnnotationTypesAll = response.annotationTypes;
     191    if (response.annotationTypesCategory) currentTargetAnnotationTypesCategory = response.annotationTypesCategory;
     192    if (response.extensionColumns) currentTargetExtensionColumns = response.extensionColumns;
     193   
     194    configure.updateColumnsList();
     195  }
     196 
     197  configure.childItemTypeOnChange = function(event)
     198  {
     199    // Reset everything that depends on the child item type
     200    configure.clearColumns();
     201    configure.clearList('childSubtype');
     202   
     203    var frm = document.forms['relatedItems'];
     204    var targetItemType = frm.targetItemType.value;
     205    var childItemType = frm.childItemType.value;
     206   
     207    if (targetItemType && childItemType)
    103208    {
    104209      var url = 'ajax.jsp?ID='+App.getSessionId();
    105       url += '&cmd=GetSubtypesAndAnnotationTypes&itemType='+encodeURIComponent(targetItemType);
     210      url += '&cmd=GetSubtypesAndAnnotationTypes&itemType='+encodeURIComponent(childItemType);
     211      url += '&sourceItemType='+encodeURIComponent(targetItemType);
     212      url += '&direction=CHILD';
    106213     
    107214      var request = Ajax.getXmlHttpRequest();
    108215      request.open("GET", url, true);
    109216      request.send(null);
    110       Ajax.setReadyStateHandler(request, configure.typeInfoLoaded, configure.typeInfoLoaded);
    111     }
    112   }
    113  
    114   configure.typeInfoLoaded = function(request)
     217      Ajax.setReadyStateHandler(request, configure.childTypeInfoLoaded, configure.childTypeInfoLoaded);
     218    }
     219    else
     220    {
     221      configure.updateColumnsList();
     222    }
     223  }
     224
     225  configure.childSubtypeOnChange = function(event)
     226  {
     227    // Reset everything that depends on the child subtype
     228    configure.clearColumns();
     229
     230    var frm = document.forms['relatedItems'];
     231    var targetItemType = frm.targetItemType.value;
     232    var childItemType = frm.childItemType.value;
     233    var category = frm.childSubtype.selectedIndex > 0 ? frm.childSubtype[frm.childSubtype.selectedIndex].text : null;
     234    if (targetItemType && childItemType && category)
     235    {
     236      var url = 'ajax.jsp?ID='+App.getSessionId();
     237      url += '&cmd=GetAnnotationTypesForCategory&itemType='+encodeURIComponent(childItemType);
     238      url += '&sourceItemType='+encodeURIComponent(targetItemType);
     239      url += '&category='+encodeURIComponent(category);
     240     
     241      var request = Ajax.getXmlHttpRequest();
     242      request.open("GET", url, true);
     243      request.send(null);
     244      Ajax.setReadyStateHandler(request, configure.childTypeInfoLoaded, configure.childTypeInfoLoaded);
     245    }
     246  }
     247
     248  configure.childTypeInfoLoaded = function(request)
    115249  {
    116250    var response = JSON.parse(request.responseText);
     
    120254      return;
    121255    }
    122    
    123     var frm = document.forms['relatedItems'];
     256    var frm = document.forms['relatedItems'];
     257
    124258    if (response.subtypes)
    125259    {
    126       currentSubtypes = response.subtypes;
    127       frm.subtype.length = 1;
    128       for (var i = 0; i < currentSubtypes.length; i++)
     260      currentChildSubtypes = response.subtypes;
     261      frm.childSubtype.length = 1;
     262      for (var i = 0; i < currentChildSubtypes.length; i++)
    129263      {
    130         var st = currentSubtypes[i];
    131         frm.subtype[frm.subtype.length] = new Option(st.name, st.id);
     264        var st = currentChildSubtypes[i];
     265        frm.childSubtype[frm.childSubtype.length] = new Option(st.name, st.id);
    132266      }
    133       frm.subtype.selectedIndex = 0;
    134     }
    135     if (response.annotationTypes) currentAnnotationTypesAll = response.annotationTypes;
    136     if (response.annotationTypesCategory) currentAnnotationTypesCategory = response.annotationTypesCategory;
     267      frm.childSubtype.selectedIndex = 0;
     268      frm.childSubtype.disabled = currentChildSubtypes.length == 0;
     269    }
     270   
     271    if (response.annotationTypes) currentChildAnnotationTypesAll = response.annotationTypes;
     272    if (response.annotationTypesCategory) currentChildAnnotationTypesCategory = response.annotationTypesCategory;
     273    if (response.extensionColumns) currentChildExtensionColumns = response.extensionColumns;
     274
    137275    configure.updateColumnsList();
    138276  }
     277
    139278 
    140279  configure.updateColumnsList = function()
     
    143282   
    144283    var targetItemType = frm.targetItemType.value;
    145     var subtype = frm.subtype.value;
    146 
    147     if (!targetItemType || !subtype)
    148     {
    149       frm.column.length = 0;
    150       return;
    151     }
    152    
    153     var annotationTypes = frm.showAllAnnotationTypes.checked ? currentAnnotationTypesAll : currentAnnotationTypesCategory;
    154     frm.column.length = 0;
    155    
     284    var targetSubtype = frm.targetSubtype.value;
     285    var childItemType = frm.childItemType.value;
     286    var childSubtype = frm.childSubtype.value;
     287
     288    // At least a target item type and a subtype must be selected
     289    if (!targetItemType || !targetSubtype) return;
     290    // If a child item type is selected, so must a child subtype
     291    if (childItemType && !childSubtype) return;
     292   
     293    var targetDirection = Data.get(frm.targetItemType[frm.targetItemType.selectedIndex], 'direction');
     294    var targetText = frm.targetSubtype[frm.targetSubtype.selectedIndex].text;
     295    if (targetDirection == 'PARENT')
     296    {
     297      Doc.element('targetPath').innerHTML = 'Parent('+targetText+')';
     298      Doc.element('targetPath').className = 'parentitem';
     299    }
     300    else
     301    {
     302      Doc.element('targetPath').innerHTML = 'Child('+targetText+')';
     303      Doc.element('targetPath').className = 'childitem';
     304    }
     305    Doc.show('targetPath', 'inline');
     306
     307    var staticCols = staticColumns[targetItemType] || [];
     308    var annotationCols = frm.showAllAnnotationTypes.checked ? currentTargetAnnotationTypesAll : currentTargetAnnotationTypesCategory;
     309    var xtCols = currentTargetExtensionColumns;
     310   
     311    if (childItemType && childSubtype)
     312    {
     313      staticCols = staticColumns[childItemType] || [];
     314      annotationCols = frm.showAllAnnotationTypes.checked ? currentChildAnnotationTypesAll : currentChildAnnotationTypesCategory;
     315      xtCols = currentChildExtensionColumns;
     316      var childText = frm.childSubtype[frm.childSubtype.selectedIndex].text;
     317      Doc.element('childPath').innerHTML = 'Child: '+childText;
     318      Doc.show('childPath', 'inline');
     319    }   
     320   
     321    var html = '';
    156322    // Add static property columns depending on itemtype
    157     var cols = staticColumns[targetItemType] || [];
    158     for (var i = 0; i < cols.length; i++)
    159     {
    160       var c = cols[i];
    161       frm.column[frm.column.length] = new Option(c.text, c.id);
    162     }
    163    
     323    for (var i = 0; i < staticCols.length; i++)
     324    {
     325      var c = staticCols[i];
     326      var text = Strings.encodeTags(c.text);
     327      html += '<div><label><input type="checkbox" data-column-id="'+c.id+'" data-column-title="'+text+'">';
     328      html += text + '</label></div>';
     329    }
     330    Doc.element('columns').innerHTML = html;
     331
    164332    // Add annotation columns
    165     for (var i = 0; i < annotationTypes.length; i++)
    166     {
    167       var at = annotationTypes[i];
    168       frm.column[frm.column.length] = new Option(at.name+" [A]", "#"+at.id);
    169     }
    170     frm.column.selectedIndex = -1;     
    171   }
    172  
     333    html = '';
     334    for (var i = 0; i < annotationCols.length; i++)
     335    {
     336      var at = annotationCols[i];
     337      var text = Strings.encodeTags(at.name);
     338      html += '<div><label><input type="checkbox" data-column-id="#+'+at.id+'" data-column-title="'+text+'">';
     339      html += text + '</label></div>';
     340    }
     341    Doc.element('annotations').innerHTML = html;
     342   
     343    // Extension columns
     344    html = '';
     345    for (var i = 0; i < xtCols.length; i++)
     346    {
     347      var xtCol = xtCols[i];
     348      var id = "!"+xtCol.id+"#"+xtCol.columnId;
     349      var text = Strings.encodeTags(xtCol.name);
     350      if (xtCol.id.indexOf('net.sf.basedb.clients.web.related-item-columns') == 0)
     351      {
     352        // Special case when the extension is the related-item extionsion
     353        // It has built-in support for two hops and doen't have to use the
     354        // extension mechanism recursively
     355        id = xtCol.columnId;
     356      }
     357      html += '<div title="'+Strings.encodeTags(xtCol.tooltip)+'">';
     358      html += '<label><input type="checkbox" data-column-id="'+id+'" data-column-title="'+text+'">';
     359      html += (xtCol.nameHTML || text) + '</label></div>';
     360    }
     361    Doc.element('extensions').innerHTML = html;
     362  }
    173363 
    174364  configure.addColumn = function(event)
     
    178368    var data = {};
    179369    data.targetItemType = frm.targetItemType.value;
    180     data.subtype = frm.subtype.value;
     370    data.targetSubtype = frm.targetSubtype.value;
     371    data.childItemType = frm.childItemType.value;
     372    data.childSubtype = frm.childSubtype.value;
    181373
    182374    if (!data.targetItemType)
     
    185377      return;
    186378    }
    187    
    188     if (!data.subtype)
    189     {
    190       Forms.showNotification('subtype', 'Please select a subtype!');
    191       return;
    192     }
    193     data.subtypeName = frm.subtype[frm.subtype.selectedIndex].text;
     379    if (!data.targetSubtype)
     380    {
     381      Forms.showNotification('targetSubtype', 'Please select a subtype!');
     382      return;
     383    }
     384    if (data.childItemType && !data.childSubtype)
     385    {
     386      Forms.showNotification('childSubtype', 'Please select a subtype!');
     387      return;
     388    }
     389   
     390    data.targetSubtypeName = frm.targetSubtype[frm.targetSubtype.selectedIndex].text;
    194391    data.direction = Data.get(frm.targetItemType[frm.targetItemType.selectedIndex], 'direction');
    195392
     393    var propertyPrefix = '/'+data.direction+'/'+data.targetItemType+'/'+data.targetSubtype+'/';
     394    var titlePrefix = data.targetSubtypeName;
     395    if (data.childItemType)
     396    {
     397      data.childSubtypeName = frm.childSubtype[frm.childSubtype.selectedIndex].text;
     398      propertyPrefix += '/CHILD/'+data.childItemType+'/'+data.childSubtype+'/';
     399      titlePrefix += '.'+data.childSubtypeName;
     400    }
     401
    196402    var numSelected = 0;
    197     for (var i = 0; i < frm.column.length; i++)
    198     {
    199       if (frm.column[i].selected)
     403    var allColumns = document.querySelectorAll("input[data-column-id]");
     404    for (var i = 0; i < allColumns.length; i++)
     405    {
     406      if (allColumns[i].checked)
    200407      {
    201         var col = frm.column[i];
    202         data.column = col.value;
    203         data.property = '/'+data.direction+'/'+data.targetItemType+'/'+data.subtype+'/'+data.column;
    204         data.title = data.subtypeName + (col.value == '.' ? '' : '.'+col.text);
     408        var col = allColumns[i];
     409        data.column = Data.get(col, 'column-id');
     410        data.property = propertyPrefix+data.column;
     411        data.title = titlePrefix + (data.column == '.' ? '' : '.'+Data.get(col, 'column-title'));
    205412        Events.sendCustomEvent(window.opener.Doc.element('selectRelatedItemColumn'), 'base-selected', data);
    206413        numSelected++;
     414        // Remove the column from the list
     415        var div = col.parentNode.parentNode;
     416        div.parentNode.removeChild(div);
    207417      }
    208418    }
     
    210420    if (numSelected == 0)
    211421    {
    212       Forms.showNotification('column', 'Please select a column!');
    213       return;
    214     }
    215    
    216     for (var i = frm.column.length-1; i >= 0; i--)
    217     {
    218       if (frm.column[i].selected) frm.column[i] = null;
     422      Forms.showNotification('columnstable', 'Please select at least one column!', null, 'pointer-below');
     423      return;
    219424    }
    220425    Doc.element('added-column-msg').innerHTML = numSelected == 1 ?
    221426      '1 column has been added to the table.' : numSelected + ' columns have been added to the table.';
    222     Doc.show('added-column-msg');
     427    Doc.show('added-column-msg', 'table');
    223428    event.stopPropagation(); // To prevent the hideMessage() method from executing
     429    setTimeout(configure.hideMessage, 2000);
    224430  }
    225431 
  • trunk/www/common/columns/add_relateditem_column.jsp

    r7842 r8083  
    4343%>
    4444<base:page type="popup" title="Add parent or child item column">
    45 <base:head scripts="~add_relateditem_column.js" />
     45<base:head scripts="~add_relateditem_column.js">
     46<style>
     47th
     48{
     49  min-width: 10em;
     50}
     51select
     52{
     53  width: calc(100% - 1em);
     54  min-width: 15em;
     55}
     56
     57.columnstable
     58{
     59  width: 100%;
     60}
     61
     62.columnstable tr
     63{
     64  vertical-align: top;
     65}
     66
     67.columnstable th
     68{
     69  text-align: left;
     70  padding: 2px 4px;
     71}
     72
     73.columnstable td
     74{
     75  text-align: left;
     76  padding: 4px 1px;
     77}
     78
     79.columnstable td > div
     80{
     81  margin-bottom: 4px;
     82  white-space: nowrap;
     83}
     84
     85#added-column-msg
     86{
     87  position: absolute;
     88  bottom: 1em;
     89  left: 0;
     90  right: 0;
     91  display: table;
     92  margin-left: auto;
     93  margin-right: auto;
     94}
     95
     96.parentitem::before
     97{
     98  content: url('../../images/parent-item.png');
     99  margin: 0 2px;
     100  vertical-align: -2px;
     101}
     102.childitem::before
     103{
     104  content: url('../../images/child-item.png');
     105  margin: 0 2px;
     106  vertical-align: -2px;
     107}
     108
     109.columnsFrom
     110{
     111  font-weight: bold;
     112}
     113
     114.columnsFrom > span
     115{}
     116</style>
     117</base:head>
    46118<base:body>
    47119  <h1>Add parent or child item column <base:help helpid="columns.configure.add_relateditem_column" /></h1>
     
    52124 
    53125  <div class="content">
    54     <table class="fullform input100 smaller bottomborder">
     126    <div class="absolutefull" >
     127    <table class="fullform input100 bottomborder">
    55128    <tr>
    56       <th>Item type</th>
    57       <td>
    58         <select name="targetItemType" id="targetItemType" style="min-width: 15em;">
     129      <th>Parent/child</th>
     130      <td>
     131        <select name="targetItemType" id="targetItemType" class="required">
    59132          <option value="">- select -
    60133          <%
     
    74147        </select>
    75148      </td>
     149      <td style="white-space: nowrap;">with link to</td>
     150      <td>
     151        <select name="childItemType" id="childItemType" disabled>
     152          <option value="">- optional -
     153        </select>
     154      </td>
     155      <td>
     156      </td>
    76157    </tr>
    77158    <tr>
    78159      <th>Subtype</th>
    79160      <td>
    80         <select name="subtype" id="subtype" style="min-width: 15em;">
     161        <select name="targetSubtype" id="targetSubtype" class="required" disabled>
    81162          <option value="">- select -
    82163        </select>
    83         <label><input type="checkbox" id="showAllAnnotationTypes" name="showAllAnnotationTypes">Show annotation types for all subtypes</label>
     164      </td>
     165      <td>subtype</td>
     166      <td>
     167        <select name="childSubtype" id="childSubtype" disabled>
     168          <option value="">- select -
     169        </select>
    84170      </td>
    85171    </tr>
    86172    <tr class="dynamic">
    87       <th>Column</th>
    88       <td>
    89         <select name="column" id="column" style="min-width: 25em; height: calc(100% - 4em);" multiple></select>
    90         <div id="added-column-msg" class="messagecontainer note" style="display:none; margin: 0.5em 0;"></div>
    91       </td>
     173      <th></th>
     174      <td></td>
     175      <td></td>
     176      <td></td>
    92177    </tr>
    93178    </table>
     179    </div>
     180   
     181    <div class="absolutefull" style="top: 4em; left: 10em;">
     182      <div class="columnsFrom" style="height: 1.5em; white-space: nowrap; overflow: hidden;">
     183        <span id="targetPath"></span><span id="childPath" class="childitem" style="display: none;"></span>
     184        <label style="font-weight: normal;"><input type="checkbox" id="showAllAnnotationTypes"
     185          name="showAllAnnotationTypes">Show annotations for all subtypes</label>
     186      </div>
     187      <table id="columnstable" class="columnstable" style="width: 100%;">
     188      <thead class="bg-filled-100 topborder bottomborder">
     189        <tr>
     190          <th style="width: 33%;">Columns</th>
     191          <th style="width: 33%;">Annotations</th>
     192          <th style="width: 34%;">Extensions</th>
     193        </tr>
     194      </thead>
     195      <tbody>
     196        <tr>
     197          <td id="columns"></td>
     198          <td id="annotations"></td>
     199          <td id="extensions"></td>
     200        </tr>
     201      </tbody>
     202      </table>
     203    </div>
     204   
     205    <div id="added-column-msg" class="messagecontainer note" style="display:none;">message</div>
     206
    94207  </div>
    95208  </form>
  • trunk/www/common/columns/ajax.jsp

    r7954 r8083  
    3535  import="net.sf.basedb.core.RawDataType"
    3636  import="net.sf.basedb.core.plugin.GuiContext"
     37  import="net.sf.basedb.core.SyncFilter.SourceItemTransform"
    3738  import="net.sf.basedb.util.Values"
    3839  import="net.sf.basedb.util.formatter.Formatter"
     40  import="net.sf.basedb.util.formatter.ToStringFormatter"
    3941  import="net.sf.basedb.util.error.ThrowableUtil"
     42  import="net.sf.basedb.util.extensions.ActionIterator"
    4043  import="net.sf.basedb.util.extensions.ExtensionsInvoker"
     44  import="net.sf.basedb.util.listable.ListableUtil"
     45  import="net.sf.basedb.util.listable.SourceItemTransformerFactory"
    4146  import="net.sf.basedb.core.query.Restrictions"
    4247  import="net.sf.basedb.core.query.Expressions"
     
    4550  import="net.sf.basedb.clients.web.Base"
    4651  import="net.sf.basedb.clients.web.WebException"
     52  import="net.sf.basedb.clients.web.util.HTML"
    4753  import="net.sf.basedb.clients.web.extensions.JspContext"
    4854  import="net.sf.basedb.clients.web.extensions.ExtensionsControl"
    4955  import="net.sf.basedb.clients.web.extensions.list.ListColumnAction"
    5056  import="net.sf.basedb.clients.web.extensions.list.ListColumnUtil"
     57  import="net.sf.basedb.clients.web.extensions.list.RelatedItemExtensionColumn"
     58  import="net.sf.basedb.clients.web.extensions.list.RelatedItemColumn.Specification"
    5159  import="org.json.simple.JSONObject"
    5260  import="org.json.simple.JSONArray"
     
    7280  {
    7381    dc = sc.newDbControl(":Get subtypes and annotation types");
    74 
     82   
     83    Item sourceItemType = Item.valueOf(request.getParameter("sourceItemType"));
     84    String direction = Values.getString(request.getParameter("direction"), "PARENT");
     85
     86    JSONArray jsonAllowedChildTypes = new JSONArray();
     87    json.put("childTypes", jsonAllowedChildTypes);
     88    if ("PARENT".equals(direction))
     89    {
     90      SourceItemTransformerFactory factory = ListableUtil.getTransformerFactory(itemType);
     91      List<Item> allowedChildItems = factory.getSupportedSourceItems(SourceItemTransform.CHILD_TO_PARENT);
     92      for (Item childType : allowedChildItems)
     93      {
     94        JSONObject jsonCt = new JSONObject();
     95        jsonCt.put("id", childType.name());
     96        jsonCt.put("name", childType.toString());
     97        jsonAllowedChildTypes.add(jsonCt);
     98      }
     99    }
     100   
    75101    JSONArray jsonSubtypes = new JSONArray();
    76102    json.put("subtypes", jsonSubtypes);
     
    109135    json.put("annotationTypes", jsonAnnotationTypes);
    110136   
     137    Specification spec = Specification.parse(sourceItemType, "/"+direction+"/"+itemType.name()+"/*/*");
     138    GuiContext guiContext = GuiContext.list(itemType, RelatedItemExtensionColumn.SUBCONTEXT);
     139    JspContext jspContext = ExtensionsControl.createContext(dc, pageContext, guiContext, spec);
     140    ExtensionsInvoker<ListColumnAction<BasicItem,?>> columnsInvoker = ListColumnUtil.useExtensions(jspContext);
     141    ActionIterator<ListColumnAction<BasicItem,?>> it = columnsInvoker.iterate();
     142    JSONArray jsonExtensions = new JSONArray();
     143    while (it.hasNext())
     144    {
     145      ListColumnAction<BasicItem,?> col = it.next();
     146      JSONObject jsonXt = new JSONObject();
     147      jsonXt.put("id", it.getExtension().getId());
     148      jsonXt.put("columnId", col.getId());
     149      jsonXt.put("name", HTML.stripMarkup(col.getTitle()));
     150      jsonXt.put("nameHTML", col.getTitle());
     151      jsonXt.put("tooltip", col.getTooltip());
     152      jsonExtensions.add(jsonXt);
     153    }
     154    json.put("extensionColumns", jsonExtensions);
    111155  }
    112156  else if ("GetAnnotationTypesForCategory".equals(cmd))
     
    128172      }
    129173    }
     174   
    130175    json.put("annotationTypesCategory", jsonAnnotationTypes);
    131176  }
    132177  else if ("GetLazyItemColumns".equals(cmd))
    133178  {
    134     String subContext = Values.getStringOrNull(request.getParameter("subcontext"));
     179    String subContext = Values.getString(request.getParameter("subcontext"));
    135180    GuiContext guiContext = GuiContext.list(itemType, subContext);
     181    ItemContext cc = sc.getCurrentContext(itemType, subContext);
    136182    Integer[] items = Values.getInt(request.getParameter("items").split(","));
     183    int remaining = Values.getInt(request.getParameter("remaining"), -1);
    137184    dc = sc.newDbControl(":Get lazy columns");
    138185   
    139186    JspContext jspContext = ExtensionsControl.createContext(dc, pageContext, guiContext, null);
     187    jspContext.loadAttributes(cc, "lazy-related-items-attributes");
     188    if (remaining == 0) cc.removeObject("lazy-related-items-attributes");
     189   
    140190    jspContext.setAttribute("lazy-loading", false);
    141191    ExtensionsInvoker<ListColumnAction<BasicItem, ?>> columnsInvoker = ListColumnUtil.useExtensions(jspContext);
     
    159209          @SuppressWarnings("unchecked")
    160210          Formatter<Object> f = (Formatter<Object>)col.getFormatter();
     211          if (f == null) f = new ToStringFormatter<>();
    161212          jsonCols.add(f.format(col.getValue(dc, item)));
    162213        }
  • trunk/www/common/columns/configure.js

    r7982 r8083  
    257257    var url = 'add_relateditem_column.jsp?ID='+App.getSessionId();
    258258    url += '&item_type='+Data.get('page-data', 'item-type');
    259     Dialogs.openPopup(url, 'SelectRelatedItemColumn', 600, 400);
     259    Dialogs.openPopup(url, 'SelectRelatedItemColumn', 750, 500);
    260260  }
    261261
  • trunk/www/filemanager/files/list_files.jsp

    r7983 r8083  
    10841084                          list="<%=loader.getValues()%>"
    10851085                          suffix="<%=loader.getUnitSymbol()%>"
    1086                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     1086                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    10871087                        /><%
    10881088                      }
  • trunk/www/include/scripts/lazy-items.js

    r7982 r8083  
    4141  lazy.initLazyItems = function()
    4242  {
    43     lazyElements = document.getElementsByClassName('lazy-item');
     43    lazyElements = document.querySelectorAll('.lazy-item');
    4444    if (lazyElements.length > 0)
    4545    {
     
    7878      if (subContext) url += '&subcontext='+encodeURIComponent(subContext);
    7979      url += '&items='+lazyItems.join(',');
     80      url += '&remaining='+(lazyElements.length-lazyNo);
    8081      request.open("GET", url, true);
    8182      request.send(null);
     
    110111      for (var col = 0; col < item.data.length; col++)
    111112      {
    112         var e = Doc.element('lazy-item-'+col+'-'+itemId);
     113        var e = Doc.element('lazy-item-'+col+'-'+itemId).parentNode;
    113114        if (e)
    114115        {
    115116          e.innerHTML = item.data[col];
    116           Doc.removeClass(e, 'loading');
    117           Doc.removeClass(e, 'loaded');
    118117          Doc.autoInitElements(e);
    119118        }
  • trunk/www/include/styles/main.css

    r7993 r8083  
    210210
    211211/* Generic button, standalone or in a toolbar, define colors only */
    212 .button, .tab
     212.button, .tab, ::file-selector-button
    213213{
    214214  background-color: #E8E8E8;
     
    223223
    224224.interactable:hover, .interactable:focus, .interactable.active,
    225 input:hover, textarea:hover, select:hover, input:focus, textarea:focus, select:focus
     225input:hover, textarea:hover, select:hover, input:focus, textarea:focus, select:focus, ::file-selector-button:hover
    226226{
    227227  border-color: #2288AA;
     
    253253
    254254/* A single standalone button */
    255 .basicbutton 
     255.basicbutton, ::file-selector-button
    256256{
    257257  display: inline-block;
     
    265265}
    266266
     267/* We need some additional styling for resetting */
     268::file-selector-button
     269{
     270  border-style: solid;
     271  border-color: inherit;
     272  margin-right: 4px;
     273  padding-top: 2px;
     274}
     275
    267276.buttongroup .basicbutton
    268277{
     
    277286
    278287/* Highlight the button when the mouse is over it */
    279 .basicbutton:hover, .basicbutton:focus
     288.basicbutton:hover, .basicbutton:focus, ::file-selector-button:hover
    280289{
    281290  /* 1+1=0+2 so that the button is not moving */
     
    284293  cursor: pointer;
    285294}
     295
     296::file-selector-button:hover
     297{
     298  /* Since we have 4px without hover */
     299  margin-right: 3px;
     300}
     301
    286302
    287303.basicbutton.square
     
    727743{
    728744  background-color: #F8F8E8 !important;
     745  color: inherit !important;
    729746  border-top: 1px solid #2288AA !important;
    730747  border-bottom: 1px solid #2288AA !important;
     
    737754{
    738755  background-color: #F8F8E8 !important;
     756  color: inherit !important;
    739757  border-top: 1px solid #2288AA !important;
    740758  border-bottom: 1px solid #2288AA !important;
  • trunk/www/include/styles/table.css

    r8026 r8083  
    260260}
    261261
    262 .itemlist div.data th.relateditemcol.parentitem::before
     262.itemlist div.data th.relateditemcol span.parentitem::before
    263263{
    264264  content: url('../../images/parent-item.png');
    265 }
    266 
    267 .itemlist div.data th.relateditemcol.childitem::before
     265  margin: 0 1px;
     266  vertical-align: -1px;
     267}
     268
     269.itemlist div.data th.relateditemcol span.childitem::before
    268270{
    269271  content: url('../../images/child-item.png');
     272  margin: 0 1px;
     273  vertical-align: -1px;
    270274}
    271275
  • trunk/www/lims/arraybatches/list_batches.jsp

    r7982 r8083  
    591591                          list="<%=loader.getValues()%>"
    592592                          suffix="<%=loader.getUnitSymbol()%>"
    593                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     593                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    594594                        /><%
    595595                      }
  • trunk/www/lims/arraydesigns/list_designs.jsp

    r8026 r8083  
    759759                          list="<%=loader.getValues()%>"
    760760                          suffix="<%=loader.getUnitSymbol()%>"
    761                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     761                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    762762                        /><%
    763763                      }
  • trunk/www/lims/arrayslides/list_slides.jsp

    r7982 r8083  
    576576                          list="<%=loader.getValues()%>"
    577577                          suffix="<%=loader.getUnitSymbol()%>"
    578                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     578                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    579579                        /><%
    580580                      }
  • trunk/www/lims/plates/list_plates.jsp

    r7982 r8083  
    651651                          list="<%=loader.getValues()%>"
    652652                          suffix="<%=loader.getUnitSymbol()%>"
    653                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     653                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    654654                        /><%
    655655                      }
  • trunk/www/lims/plates/wells/list_wells.jsp

    r7982 r8083  
    652652                          list="<%=loader.getValues()%>"
    653653                          suffix="<%=loader.getUnitSymbol()%>"
    654                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     654                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    655655                        /><%
    656656                      }
  • trunk/www/my_base/projects/list_projects.jsp

    r7982 r8083  
    472472                          list="<%=loader.getValues()%>"
    473473                          suffix="<%=loader.getUnitSymbol()%>"
    474                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     474                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    475475                        /><%
    476476                      }
  • trunk/www/views/derivedbioassays/list_bioassays.jsp

    r7982 r8083  
    818818                          list="<%=loader.getValues()%>"
    819819                          suffix="<%=loader.getUnitSymbol()%>"
    820                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     820                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    821821                        /><%
    822822                      }
  • trunk/www/views/experiments/bioassays/list_bioassays.jsp

    r7982 r8083  
    565565                          list="<%=loader.getValues()%>"
    566566                          suffix="<%=loader.getUnitSymbol()%>"
    567                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     567                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    568568                        /><%
    569569                      }
  • trunk/www/views/experiments/bioassaysets/analysis_tree.jsp

    r7982 r8083  
    846846                              list="<%=loader.getValues()%>"
    847847                              suffix="<%=loader.getUnitSymbol()%>"
    848                               clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     848                              clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    849849                            /><%
    850850                          }
  • trunk/www/views/experiments/list_experiments.jsp

    r7982 r8083  
    637637                          list="<%=loader.getValues()%>"
    638638                          suffix="<%=loader.getUnitSymbol()%>"
    639                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     639                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    640640                        /><%
    641641                      }
  • trunk/www/views/itemlists/list_lists.jsp

    r7982 r8083  
    617617                          list="<%=loader.getValues()%>"
    618618                          suffix="<%=loader.getUnitSymbol()%>"
    619                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     619                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    620620                        /><%
    621621                      }
  • trunk/www/views/itemlists/members/list_members.jsp

    r7982 r8083  
    607607                          list="<%=loader.getValues()%>"
    608608                          suffix="<%=loader.getUnitSymbol()%>"
    609                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     609                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    610610                        /><%
    611611                      }
  • trunk/www/views/physicalbioassays/list_bioassays.jsp

    r7982 r8083  
    766766                          list="<%=loader.getValues()%>"
    767767                          suffix="<%=loader.getUnitSymbol()%>"
    768                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     768                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    769769                        /><%
    770770                      }
  • trunk/www/views/rawbioassays/list_rawbioassays.jsp

    r7982 r8083  
    875875                          list="<%=loader.getValues()%>"
    876876                          suffix="<%=loader.getUnitSymbol()%>"
    877                           clazz="<%=psInfo.overridesDefaultAnnotation() ? "ps-annotation" : null%>"
     877                          clazz="<%=psInfo.hasProjectSpecificAnnotation() ? "ps-annotation" : null%>"
    878878                        /><%
    879879                      }
Note: See TracChangeset for help on using the changeset viewer.