Selaa lähdekoodia

Merge branch '7.x-3.x' into 531-t3-relationship_widget

Lacey Sanderson 6 vuotta sitten
vanhempi
commit
8d8d6fbc9f
53 muutettua tiedostoa jossa 1225 lisäystä ja 590 poistoa
  1. 4 4
      .travis.yml
  2. 2 42
      README.md
  3. 324 216
      composer.lock
  4. 1 0
      docs/dev_guide/custom_field.rst
  5. 113 0
      docs/dev_guide/custom_field/custom_widget.rst
  6. 1 3
      docs/user_guide.rst
  7. BIN
      docs/user_guide/configuring_page_display.5.png
  8. 14 0
      docs/user_guide/content_types.rst
  9. 0 0
      docs/user_guide/content_types/configuring_page_display.1.png
  10. 0 0
      docs/user_guide/content_types/configuring_page_display.2.png
  11. 0 0
      docs/user_guide/content_types/configuring_page_display.3.rearrange.png
  12. 0 0
      docs/user_guide/content_types/configuring_page_display.4.png
  13. 4 14
      docs/user_guide/content_types/configuring_page_display.rst
  14. 0 0
      docs/user_guide/content_types/creating_content.create1.png
  15. 0 0
      docs/user_guide/content_types/creating_content.create2.png
  16. 0 0
      docs/user_guide/content_types/creating_content.create3.png
  17. 0 0
      docs/user_guide/content_types/creating_content.create4.png
  18. 0 0
      docs/user_guide/content_types/creating_content.create5.png
  19. 0 0
      docs/user_guide/content_types/creating_content.create6.png
  20. 0 0
      docs/user_guide/content_types/creating_content.create7.png
  21. 0 0
      docs/user_guide/content_types/creating_content.rst
  22. BIN
      docs/user_guide/content_types/field_loading.empty_ajax.png
  23. 26 0
      docs/user_guide/content_types/field_loading.rst
  24. 0 0
      docs/user_guide/content_types/setting_page_urls.1.png
  25. 0 0
      docs/user_guide/content_types/setting_page_urls.2.png
  26. 0 0
      docs/user_guide/content_types/setting_page_urls.rst
  27. 7 0
      docs/user_guide/install_tripal/manual_install/install_prereqs.rst
  28. 2 0
      tests/.travis.env
  29. 1 0
      tests/example.env
  30. 59 0
      tests/tripal_ws/http/TripalWebServicesContentTest.php
  31. 0 16
      tripal/api/tripal.entities.api.inc
  32. 53 0
      tripal/api/tripal.fields.api.inc
  33. 84 56
      tripal/includes/TripalBundleUIController.inc
  34. 6 5
      tripal/includes/TripalEntityController.inc
  35. 46 16
      tripal/includes/TripalEntityUIController.inc
  36. 8 0
      tripal/includes/TripalFields/TripalFieldWidget.inc
  37. 100 36
      tripal/includes/tripal.entity.inc
  38. 0 26
      tripal/includes/tripal.fields.inc
  39. 202 21
      tripal/theme/js/tripal.js
  40. 22 0
      tripal/tripal.install
  41. 38 40
      tripal/tripal.module
  42. 29 33
      tripal_chado/api/tripal_chado.api.inc
  43. 2 1
      tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map.inc
  44. 0 6
      tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc
  45. 0 6
      tripal_chado/includes/TripalFields/sio__annotation/sio__annotation.inc
  46. 5 2
      tripal_chado/includes/TripalImporter/OBOImporter.inc
  47. 4 0
      tripal_chado/includes/tripal_chado.cv.inc
  48. 12 2
      tripal_chado/includes/tripal_chado.vocab_storage.inc
  49. 10 18
      tripal_chado/tripal_chado.module
  50. 4 0
      tripal_ds/theme/css/tripal_ds.css
  51. 1 0
      tripal_ds/theme/js/tripal_ds.js
  52. 35 21
      tripal_ds/tripal_ds.module
  53. 6 6
      tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc

+ 4 - 4
.travis.yml

@@ -12,7 +12,7 @@ php:
   - 7.1
 
 env:
-  - BASE_URL="http://localhost:8080"
+  - BASE_URL="http://127.0.0.1:8080"
 
 install:
   - composer global require drush/drush:8
@@ -31,6 +31,8 @@ before_script:
   - drush dl drupal-7 -y
   - mv drupal-7* drupal
   - cd drupal
+  # Run the php server
+  - php -S 127.0.0.1:8080 &
   - drush si -y --db-url='pgsql://postgres:dbpass@localhost:5432/test_db'
                 --account-name='admin'
                 --account-pass='admin_pass'
@@ -45,9 +47,6 @@ before_script:
   - drush en -y field_group, field_group_table, field_formatter_class, field_formatter_settings, ctools, date, devel,
               ds, link, entity, libraries, redirect, token uuid, jquery_update, views, webform
 
-  # Run the drush server
-  - drush runserver localhost:8080 &
-
 script:
   # Link our repo to the modules directory
   - mv ../tripal sites/all/modules/tripal
@@ -73,6 +72,7 @@ script:
 
   # Run PHPUnit tests
   - composer update
+  - cp tests/.travis.env tests/.env
   - ./vendor/bin/phpunit
 
   # Test Tripal v2 to v3 upgrade steps

+ 2 - 42
README.md

@@ -57,51 +57,11 @@ are available in Tripal v3.
 
 
 # Installation
-Please follow the instructions in the online Tripal User's Guide for [Tripal v2](https://tripal.info/tutorials/v2.x/installation) or [Tripal v3](https://tripal.info/tutorials/v3.x/installation).
+Please follow the instructions in the online Tripal User's Guide for [Tripal v2](https://tripal.info/tutorials/v2.x/installation) or [Tripal v3](https://tripal.readthedocs.io/en/latest/user_guide.html).
 
 
 # Upgrade from Tripal v2.x to v3.x
-Note:  Upgrade can only be performed using the `drush` command.
-
-Note: Deprecated API functions from Tripal v1.x have been removed from Tripal
-v3.  Therefore, use of deprecated API functions in templates or custom 
-modules may cause a white screen of death (WSOD).  Check teh server logs if this
-occurs to find where deprecated functions may be used.
-
-Upgrade Instructions:
-
-Step 1: Put the site in maintenance mode.
-
-Step 2: Disable tripal modules. Disabling the core module will disable all
-other Tripal modules:
-
-  `drush pm-disable tripal_core`
-  
-Step 3: Remove old Tripal v2 package and replace with Tripal v3 package
-Step 4: Enable the tripal module
-
-  `drush pm-enable tripal`
- 
-Step 5: Enable the tripal_chado module  
-
-  `drush pm-enable tripal_chado`
-  
-Step 6:  Tripal v2 modules are now called 'legacy modules'. these are the
-modules that were disabled in step #2.  For backwards compatibility, you 
-should re-enable these modules:
-
-```
-  drush pm-enable tripal_core, tripal_views, tripal_db, tripal_cv, \
-    tripal_analysis, tripal_organism, tripal_feature, tripal_pub, \
-    tripal_stock
-```
-
-
-Be sure to enable any additional modules not included in the example
-drush command above.
-
-Step 7:  Return to your Tripal site, and click the link that appears for
-preparing Chado and launch the job.
+Please follow the [Upgrade Instructions](https://tripal.readthedocs.io/en/latest/user_guide/install_tripal/upgrade_from_tripal2.html) in the Tripal v3 User's Guide
 
 
 # Customization

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 324 - 216
composer.lock


+ 1 - 0
docs/dev_guide/custom_field.rst

@@ -42,4 +42,5 @@ The rest of this section will walk you through these steps.
 
    custom_field/select_vocab_terms
    custom_field/manual_field_creation
+   custom_field/custom_widget
    custom_field/tripal_field_generator

+ 113 - 0
docs/dev_guide/custom_field/custom_widget.rst

@@ -0,0 +1,113 @@
+Creating a Custom Widget
+========================
+
+In Drupal/Tripal terminology, **widget** refers to the form elements for a specific Tripal Field on the "Edit" form of a piece of Tripal Content. For example, the ``obi__organism`` widget creates the "Organism" drop-down on the edit form of a specific gene. All fields come with a default widget; however, you can create a custom widget if the default one doesn't meet your needs.
+
+.. note::
+  This guide is going to assume you already have your widget file created. For more information, see :doc:`manual_field_creation` or, :doc:`tripal_field_generator`.
+
+.. note::
+	If you are only creating a widget and not the whole field, you still need to follow the expected directory structure. For example, if your widget is going to be named ``obi__organism_fancy`` then your file would be ``[your_module]/includes/TripalField/obi__organism_fancy/obi__organism_fancy_widget.inc``.
+	
+The Form
+--------
+
+The form elements of your widget are defined in the ``form()`` method of your widget according to the `Drupal Form API <https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x>`_. As such the ``$widget`` variable is actually a nested associative array describing what the widget portion of the form should look like. For example, the following is how the ``obi__organism`` widget creates the drop-down.
+
+.. code-block:: php
+
+  /**
+   * @see TripalFieldWidget::form()
+   */
+  public function form(&$widget, &$form, &$form_state, $langcode, $items, $delta, $element) {
+
+    $field_name = $this->field['field_name'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $linker_field = 'chado-' . $field_table . '__organism_id';
+
+    // The value presented to the user via load.
+    // If $items['delta']['value'] is set then we are updating and already have this
+    // information. As such, simply save it again.
+    $widget['value'] = array(
+      '#type' => 'value',
+      '#value' => array_key_exists($delta, $items) ? $items[$delta]['value'] : '',
+    );
+
+    // Pull out the value previously saved to be used as the default.
+    $organism_id = 0;
+    if (count($items) > 0 and array_key_exists($linker_field, $items[0])) {
+      $organism_id = $items[0][$linker_field];
+    }
+    
+    // Define a drop-down form element where the options are organisms retrieved using
+    // the Tripal API, the default is what we looked up above, and the title and 
+    // description are those set when defining the field.
+    $widget[$linker_field] = array(
+      '#type' => 'select',
+      '#title' => $element['#title'],
+      '#description' => $element['#description'],
+      '#options' => chado_get_organism_select_options(FALSE),
+      '#default_value' => $organism_id,
+      '#required' => $element['#required'],
+      '#delta' => $delta,
+    );
+
+  }
+
+At a minimum, the form must have a ``value`` element.  For Tripal, the ``value`` element of a field always corresponds to the value that is presented to the end-user either directly on the page (with formatting) or via web services, or some other mechanism. Convention is to store the value of the field as a hidden ``value`` form element as is shown in the above example. 
+
+.. note::
+	For more information on how to use the Drupal Form API, check out the `official Drupal Documentation <https://www.drupal.org/docs/7/api/form-api>`_.
+
+.. note::
+	The current item is saved in ``$items[$delta]`` as an array where the keys will match those set by the field ``load()`` function.
+
+
+Validation
+----------
+
+The ``validate()`` function of your widget allows you to confirm that the values entered by the user are valid. It is recommended to consider each form element you created above and consider what is required for that element to be entered "correctly". For example, for an organism drop-down, the organism chosen must exist in our chado database (since this is a ``ChadoFieldWidget``). Luckily this doesn't need to be validated since Drupal ensures only elements in our select list are chosen.
+
+.. warning::
+	The ``value`` key of this field must be set in the ``$form_state['values']`` array to a **TRUE** value (e.g. a string or non-zero integer) anytime data is entered by the user. 
+	
+.. note::
+	For more information on how to validate your data, see the official `Drupal Form Validation Documentation <https://www.drupal.org/docs/7/creating-custom-modules/validating-the-data>`_
+   
+Saving the Data
+---------------
+
+The Drupal Storage Backend handles saving of your widget data. As such, **you do not and should not insert, update or delete the data yourself**. It should happen automatically, assuming you've followed the conventions of the specific storage backend. 
+
+Chado Fields utilize the chado storage backend to save your data. Thus to ensure your data is saved, you set the columns of your chado table to the values you want them set via the ``$form_state['values']`` array using the ``chado-[table]__[column]`` convention. This should be done at the end of the validation function above, if the data submitted is valid. 
+
+For our ``obi__organism`` example, the drop-down returns the chado organism_id of the record chosen by the user. We would like to save that as the organism_id of the chado table the field references, which the following code specifies.
+
+.. code-block:: php
+
+  /**
+   * @see TripalFieldWidget::validate()
+   */
+  public function validate($element, $form, &$form_state, $langcode, $delta) {
+
+    $field_name = $this->field['field_name'];
+    $field_table = $this->instance['settings']['chado_table'];
+    $linker_field = 'chado-' . $field_table . '__organism_id';
+
+    //...
+    // Validate your data here
+    //...
+
+    // In this case, if you have an organism_id, then your user selected this field.
+    $organism_id = $form_state['values'][$field_name]['und'][0][$linker_field];
+    if ($organism_id > 0) {
+      $form_state['values'][$field_name]['und'][0]['value'] = $organism_id;
+      // This is where we tell the storage backend what we want to save.
+      // Specifically, that we want to save $organism_id to $field_table.organism_id
+      $form_state['values'][$field_name]['und'][$delta][$linker_field] = $organism_id;
+    }
+  }
+
+Drupal typically does not provide a submit hook for fields because, as mentioned above, saving should be done by the storage backend. However, the TripalField provides a ``TripalFieldWidget::submit()`` to allow for behind-the-scenes actions to occur. This function should never be used for updates, deletes or inserts for the Chado table associated with the field as these actions should be handled by the storage backend. 
+
+However, it is permissible to perform inserts, updates or deletions within Chado using this function.  Those operations can be performed if needed but on other tables not directly associated with the field. An example is the ``chado.feature_synonym`` table.  The ``chado_linker__synonym`` field allows the user to provide a brand new synonynm and it must add it to the chado.synonym table prior to the record in the chado.feature_synonym table.

+ 1 - 3
docs/user_guide.rst

@@ -12,9 +12,7 @@ User's Guide
    user_guide/drupal_overview
    user_guide/example_genomics
    user_guide/learn_chado
-   user_guide/creating_content
-   user_guide/setting_page_urls
-   user_guide/configuring_page_display
+   user_guide/content_types
    user_guide/mviews
    user_guide/searching
    user_guide/job_management

BIN
docs/user_guide/configuring_page_display.5.png


+ 14 - 0
docs/user_guide/content_types.rst

@@ -0,0 +1,14 @@
+Working with Content Types
+==========================
+
+New in Tripal v3 is the ability to create your own content types and manage their display!  In previous versions of Tripal a bit of PHP programming was necessary. This is no longer the case.  Tripal v3 comes pre-populated with a variety of content types and provides a web interface that allows you to create your own.  This section provides details for working with content types in Tripal.
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Table of Contents
+   :glob:
+
+   ./content_types/creating_content
+   ./content_types/setting_page_urls
+   ./content_types/configuring_page_display
+   ./content_types/field_loading

+ 0 - 0
docs/user_guide/configuring_page_display.1.png → docs/user_guide/content_types/configuring_page_display.1.png


+ 0 - 0
docs/user_guide/configuring_page_display.2.png → docs/user_guide/content_types/configuring_page_display.2.png


+ 0 - 0
docs/user_guide/configuring_page_display.3.rearrange.png → docs/user_guide/content_types/configuring_page_display.3.rearrange.png


+ 0 - 0
docs/user_guide/configuring_page_display.4.png → docs/user_guide/content_types/configuring_page_display.4.png


+ 4 - 14
docs/user_guide/configuring_page_display.rst → docs/user_guide/content_types/configuring_page_display.rst

@@ -1,16 +1,12 @@
-
-Configuring Page Display
-=========================
-
-
-This is one of the many new exciting features of Tripal v3.x. In this version of Tripal we have taken integration with Drupal Fields to a whole new level representing each piece of content (in Chado or otherwise) as a Drupal Field. What this means for site builders is unprecendented control over content display and arrangement through the administrative user interface --No more editing PHP template files to change the order, grouping or wording of content!
+Configuring Page Layout
+=======================
+This is one of the many new exciting features of Tripal v3. Integration with Drupal Fields has gone to a whole new level. Site builders have unprecendented control over the display of each piece of data through the administrative user interface. Previously, site builders were required to edit PHP template files to change the order, grouping or wording of content.
 
 You can configure the display of a given Tripal Content Type by navigating to **Structure → Tripal Content Types** and then selecting the **Manage Display** link beside the content type you would like to configure.
 
 .. image:: ./configuring_page_display.1.png
 
 
-
 The Manage Display User Interface lists each Drupal Field in the order they will be displayed on the page. Fields are grouped into Tripal Panes by the Tripal DS module and the page is automatically divided into a right and left column. By default the left column contains the table of contents which lists the Tripal Panes available to the user in the order they are listed in this UI. The following screenshots are using the Analysis Content Type for demonstatration.
 
 .. image:: configuring_page_display.2.png
@@ -39,7 +35,7 @@ There may also be data you want to collect from your user but don't want to disp
 Changing Tripal Pane Names
 --------------------------
 
-The name of a Tripal Pane is displayed both in the header of the Pane itself and in the Table of Contents. To change this name, click the gear button to the far right of the Tripal Pane you would like to change. This will bring up a blue pane of settings. Changing the Field Group Label will change the display name of the pane. For example, the following screenshot shows how you would change the "Cross References" Tripal Pane to be labeled "External Resources" instead if that it what you prefer. Then just click the Update button to see your changes take effect.
+If you enabled the `tripal_ds` module during installation then you will have what is called **Panes** into which fields can be grouped. With the `tripal_ds` module all field come pre-organizd into panes.  The name of a pane is displayed both in the header of the pane itself and in the Table of Contents. To change this name, click the gear button to the far right of the Tripal Pane you would like to change. This will bring up a blue pane of settings. Changing the Field Group Label will change the display name of the pane. For example, the following screenshot shows how you would change the "Cross References" Tripal Pane to be labeled "External Resources" instead if that it what you prefer. Then just click the Update button to see your changes take effect.
 
 .. image:: ./configuring_page_display.4.png
 
@@ -49,9 +45,3 @@ Display/Hide Tripal Panes on Page Load
 
 You can also easily control which Tripal Panes you would like displayed to the user on initial page load. By default the Summary Pane is the only one configured to show by default. However, if you would prefer for all panes or even a specific subset of panes to show by default, you can simply click the gear button to the far right of each Tripal Pane you want displayed by default and uncheck the "Hide panel on page load" checkbox. This gives you complete control over which panes you want your user to see first. If more then one pane is displayed by default then they will be shown in the order they are listed on the Manage Display UI.
 
-Display/Hide Empty Fields
--------------------------
-
-By default Tripal v3 hides all empty fields from the user. However like most behaviour in Tripal, this can be configured. If you would prefer to show all fields to the user regardless of whether there is content for that particular page, then navigate to ``Structure → Tripal Content Types`` and then click on the edit link beside the Tripal Content Type you would like to show empty fields for. Near the bottom of this form is a **Field Display** drop-down. Just change this drop-down to "show empty fields" and then click **Save Content Type**. As an example, we have changed this setting for the organism content type and, as you can see below, now you can see all fields (including empty fields like cross references and relationships) available to the organism content type.
-
-.. image:: ./configuring_page_display.5.png

+ 0 - 0
docs/user_guide/creating_content.create1.png → docs/user_guide/content_types/creating_content.create1.png


+ 0 - 0
docs/user_guide/creating_content.create2.png → docs/user_guide/content_types/creating_content.create2.png


+ 0 - 0
docs/user_guide/creating_content.create3.png → docs/user_guide/content_types/creating_content.create3.png


+ 0 - 0
docs/user_guide/creating_content.create4.png → docs/user_guide/content_types/creating_content.create4.png


+ 0 - 0
docs/user_guide/creating_content.create5.png → docs/user_guide/content_types/creating_content.create5.png


+ 0 - 0
docs/user_guide/creating_content.create6.png → docs/user_guide/content_types/creating_content.create6.png


+ 0 - 0
docs/user_guide/creating_content.create7.png → docs/user_guide/content_types/creating_content.create7.png


+ 0 - 0
docs/user_guide/creating_content.rst → docs/user_guide/content_types/creating_content.rst


BIN
docs/user_guide/content_types/field_loading.empty_ajax.png


+ 26 - 0
docs/user_guide/content_types/field_loading.rst

@@ -0,0 +1,26 @@
+Hide Empty Fields and AJAX loading
+==================================
+
+Tripal provides two additional controls for display of fields on a page:
+
+* Hiding fields with no data.
+* Loading fields using AJAX.
+
+You will find two check boxes when editing any content page that gives you these controls.  Navigate to ``Structure → Tripal Content Types`` and then click on any Tripal Content Type.  You will see options similar to the following:
+
+.. image:: ./field_loading.empty_ajax.png
+
+
+Hiding Empty Fields
+-------------------
+The previous sections of this guide instructed how to rearrange fields on a page, hide their titles, and organize them into panes.  However, while there are many fields many of them may not have any data.  All of these fields are present because the data store (e.g. Chado) has the capacity to house the type of data the fields represent, but if you did not load data appropriate for those fields then they will have no data.  
+
+By default Tripal v3 hides all empty fields from the user. However if you would prefer to show all fields to the user regardless of whether there is content for that particular page edit the content type and click the box labeled `Hide Empty Fields` and click the `Save` button at the bottom.  The next time anyone loads any page for the given content type all fields will be shown regardless if they have data.
+
+Using AJAX to Load Fields
+-------------------------
+Depending on the number of fields for your content type and the amount of data that those fields contain you may notice that page loads can take a few seconds to load. AJAX is a method to help decrease load times by allowing the page to load quickly with minimal data and allowing fields with larger amounts of data to load after the initial page load.  Tripal has this setting enabled by default. but you can disable this feature.  Similar to the check box for hiding fields, there is a check box on the content type edit page labeled `Load field using AJAX`. Remove the check for box to disable all AJAX loading of fields and save the content type settings.
+
+.. note::
+ 
+  You can control AJAX loading and hiding of empty fields differently for each conent type.

+ 0 - 0
docs/user_guide/setting_page_urls.1.png → docs/user_guide/content_types/setting_page_urls.1.png


+ 0 - 0
docs/user_guide/setting_page_urls.2.png → docs/user_guide/content_types/setting_page_urls.2.png


+ 0 - 0
docs/user_guide/setting_page_urls.rst → docs/user_guide/content_types/setting_page_urls.rst


+ 7 - 0
docs/user_guide/install_tripal/manual_install/install_prereqs.rst

@@ -41,3 +41,10 @@ Optionally, you can install the ckeditor module.  This module provides a nice WY
 
   drush pm-download ckeditor
   drush pm-enable ckeditor
+  
+Finally, we need an more recent version of JQuery that what comes with Drupal.  We can get this by installing the JQuery update module.
+
+.. code-block:: bash
+
+  drush pm-download jquery_update
+  drush pm-enable jquery_update

+ 2 - 0
tests/.travis.env

@@ -0,0 +1,2 @@
+BASE_URL=http://127.0.0.1:8080
+FAKER_LOCALE=en_US

+ 1 - 0
tests/example.env

@@ -1,2 +1,3 @@
+BASE_URL=http://localhost
 DRUPAL_ROOT=/var/www/html
 FAKER_LOCALE=en_US

+ 59 - 0
tests/tripal_ws/http/TripalWebServicesContentTest.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace Tests\tripal_ws\http;
+
+use StatonLab\TripalTestSuite\DBTransaction;
+use StatonLab\TripalTestSuite\TripalTestCase;
+
+class TripalWebServicesContentTest extends TripalTestCase{
+
+  // Uncomment to auto start and rollback db transactions per test method.
+  use DBTransaction;
+
+  /** @test */
+  public function testGettingMainContentList() {
+    $response = $this->get('web-services/content/v0.1');
+
+    // Make sure it returned valid json
+    $response->assertSuccessful();
+    $response->assertJsonStructure([
+      '@context',
+      '@id',
+      '@type',
+      'label',
+      'totalItems',
+      'member' => [
+        [
+          '@id',
+          '@type',
+          'label',
+          'description',
+        ],
+      ],
+    ]);
+  }
+
+  /** @test */
+  public function testGettingListOfEntitiesInABundle() {
+    // Get bundle label
+    $label = db_query('SELECT label FROM tripal_bundle LIMIT 1')->fetchField();
+
+    // Call /web-services/content/v0.1/[label]
+    $response = $this->get("web-services/content/v0.1/$label");
+
+    // Verify the returned JSON matches the structure
+    $response->assertSuccessful();
+    $response->assertJsonStructure([
+      '@context',
+      '@id',
+      '@type',
+      'label',
+      'totalItems',
+      'member',
+    ]);
+
+    // Verify the collection is of the correct type
+    $json = $response->json();
+    $this->assertEquals($json['label'], "$label Collection");
+  }
+}

+ 0 - 16
tripal/api/tripal.entities.api.inc

@@ -1006,22 +1006,6 @@ function tripal_get_bundle_variable($variable_name, $bundle_id, $default = FALSE
 function tripal_set_bundle_variable($variable_name, $bundle_id, $value) {
   $variable = tripal_get_variable($variable_name);
 
-  if (!$variable) {
-    if($variable_name === 'hide_empty_field'){
-      tripal_insert_variable(
-        'hide_empty_field',
-        'Structure->Tripal Content Type->edit checkbox to hide empty fields for that bundle.'
-      );
-      $variable = tripal_get_variable($variable_name);
-      if (!$variable) {
-        return FALSE;
-      }
-    }
-    else {
-      return FALSE;
-    }
-  }
-
   // And then we need to write the new format to the tripal_bundle_variables
   // table.
   $record = array(

+ 53 - 0
tripal/api/tripal.fields.api.inc

@@ -107,6 +107,59 @@ function hook_bundle_instances_info($entity_type, $bundle) {
 
 }
 
+/**
+ * Indicate if a field has an empty value.
+ *
+ * By default, all field values are attached to an entity in the form
+ * $entity->{field_name}[{language}][{delta}].   Tyipcally a field witll then
+ * have a 'value' element:  $entity->{field_name}[{language}][{delta}]['value']
+ * and if that value is empty then the field is considered empty by Tripal.
+ * By default the tripal_field-is_empty() function is used to check all
+ * fields to see if they are empty. However, this fhook can be implemented by
+ * any module to override that behavior.
+ *
+ * @param $field
+ *   A field array.
+ * @param $items
+ *   The array of items attached to entity.
+ * @param $delta
+ *   The specific value to check.  For fields with cardinality greater than
+ *   1 then each value can be checked. Defaults to 0 indicating it will check
+ *   the first value.
+ *
+ * @return
+ *   TRUE if the field value is empty for the given delta, and FALSE if not
+ *   empty.
+ *   
+ * @ingroup tripal_fields_api
+ */
+function tripal_field_is_empty($field, $items, $delta = 0) {
+  
+  // If the $items argument is empty then return TRUE.
+  if (!$items) {
+    return TRUE;
+  }
+  
+  // If the field is a tripal field storage API field and there
+  // is no value field then the field is empty.
+  if (array_key_exists('storage', $field) and 
+      array_key_exists('tripal_storage_api', $field['storage']['settings']) and 
+      !array_key_exists('value', $items[$delta])) {
+    return TRUE;
+  }
+  
+  // If there is a value field but there's nothing in it, the the field is
+  // empty.
+  if (array_key_exists($delta, $items) and 
+      array_key_exists('value', $items[$delta]) and 
+      empty($items[$delta]['value'])) {
+    return TRUE;
+  }
+  
+  // Otherwise, the field is not empty.
+  return FALSE;
+}
+
 /**
  * Retrieves a list of TripalField types.
  *

+ 84 - 56
tripal/includes/TripalBundleUIController.inc

@@ -56,7 +56,7 @@ class TripalBundleUIController extends EntityDefaultUIController {
 
     return $forms;
   }
-  
+
   /**
    * Renders the Bundle overview table
    */
@@ -64,17 +64,17 @@ class TripalBundleUIController extends EntityDefaultUIController {
     $entities = entity_load($this->entityType, FALSE, $conditions);
 
     // Sort the entities by label.
-    $sorted = [];    
+    $sorted = [];
     foreach ($entities as $entity) {
       $sorted[$entity->label] = $entity;
     }
     ksort($sorted, SORT_STRING|SORT_FLAG_CASE);
-    
+
     $rows = array();
     foreach ($sorted as $entity) {
       // Get the term for this content type
       $additional_cols = [$entity->term->name . ' (' . l($entity->accession, 'cv/lookup/' . $entity->term->vocab->vocabulary . '/' . $entity->term->accession) . ')'];
-      $rows[] = $this->overviewTableRow($conditions, 
+      $rows[] = $this->overviewTableRow($conditions,
         entity_id($this->entityType, $entity), $entity,
         $additional_cols);
     }
@@ -96,7 +96,7 @@ class TripalBundleUIController extends EntityDefaultUIController {
       'data' => t('Operations'),
       'colspan' => $colspan,
     );
-    
+
     $render = array(
       '#theme' => 'table',
       '#header' => $header,
@@ -220,16 +220,40 @@ function tripal_tripal_bundle_form($form, &$form_state, $entityDataType) {
     $form['description']['#default_value'] = tripal_get_bundle_variable('description', $bundle->id, '');
   }
 
-  $empty_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, '');
+  $hide_empty_field_var = tripal_get_bundle_variable('hide_empty_field', $bundle->id);
+  if ($hide_empty_field_var != 0) {
+    $hide_empty_field_var = TRUE;
+  }
+
+  $ajax_field_var = tripal_get_bundle_variable('ajax_field', $bundle->id);
+  if ($ajax_field_var != 0) {
+    $ajax_field_var = TRUE;
+  }
+
   $form['hide_empty_field'] = array(
-    '#type' => 'select',
-    '#title' => t('Field Display'),
-    '#options' => array(
-      'hide' => t('Hide empty fields'),
-      'show' => t('Show empty fields'),
+    '#type' => 'checkbox',
+    '#title' => t('Hide empty fields'),
+    '#description' => t('Uncheck this box if you would like to show all empty fields.'),
+    '#default_value' => $hide_empty_field_var,
+    '#weight' => 9,
+  );
+
+  $form['ajax_field'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Load field using AJAX'),
+    '#description' => t('Uncheck this box if you do not want field data to load by ajax, this may signifiantly increase page load times.'),
+    '#default_value' => $ajax_field_var,
+    '#weight' => 9,
+  );
+
+  $form['ajax_field disclaimer'] = array(
+    '#type' => 'item',
+    '#markup' => t(
+      '<p>Please note, if both "Hide empty fields" and "Load field using AJAX" 
+      are checked empty fields will be hidden using javascript which may result
+       in some jumpiness of content as the page finishes loading.</p>'
     ),
-    '#description' => t('Choose either to show or hide all empty fields.  If "Show empty fields" is selected then fields will be loaded via AJAX to help speed page loads.'),
-    '#default_value' => !empty($empty_fields) ? array($empty_fields,) : array('hide',),
+    '#weight' => 10,
   );
 
   $form['additional_settings'] = array(
@@ -347,7 +371,7 @@ function tripal_tripal_bundle_form($form, &$form_state, $entityDataType) {
   $form['url']['token_display'] = array(
     '#type' => 'fieldset',
     '#title' => t('Available Tokens'),
-    '#description' => t('Copy the token and paste it into the "URL Alias Pattern" ' . 
+    '#description' => t('Copy the token and paste it into the "URL Alias Pattern" ' .
       'text field above. Please choose tokens that will guarantee a unique URL.'),
     '#collapsible' => TRUE,
     '#collapsed' => TRUE
@@ -393,7 +417,10 @@ function tripal_tripal_bundle_form_validate($form, $form_state) {
   // VALIDATE: That there is a value passed for the hide_empty_field option. If
   // no value passed default to hide field.
   if(empty($form_state['values']['hide_empty_field'])){
-    $form_state['values']['hide_empty_field'] = 'hide';
+    $form_state['values']['hide_empty_field'] = TRUE;
+  }
+  if (empty($form_state['values']['ajax_field'])) {
+    $form_state['values']['ajax_field'] = TRUE;
   }
   // VALIDATE: The only tokens used should be those we mentioned under "Available Tokens".
   // PART 1: Set Titles.
@@ -464,6 +491,7 @@ function tripal_tripal_bundle_form_submit($form, &$form_state) {
 
     // Save the hide_empty_field setting.
     tripal_set_bundle_variable('hide_empty_field', $bundle->id, $form_state['values']['hide_empty_field']);
+    tripal_set_bundle_variable('ajax_field', $bundle->id, $form_state['values']['ajax_field']);
 
     // Save the page title format.
     tripal_save_title_format(
@@ -489,7 +517,7 @@ function tripal_tripal_bundle_form_submit($form, &$form_state) {
       $includes = array(
         module_load_include('inc', 'tripal', 'includes/tripal.bulk_update'),
       );
-      tripal_add_job('Update all aliases for content type: ' . $bundle->label, 'tripal', 
+      tripal_add_job('Update all aliases for content type: ' . $bundle->label, 'tripal',
         'tripal_update_all_urls_and_titles', $args, $user->uid, 10, $includes);
     }
     elseif ($trigger == 'Bulk update all aliases'){
@@ -503,7 +531,7 @@ function tripal_tripal_bundle_form_submit($form, &$form_state) {
       $includes = array(
         module_load_include('inc', 'tripal', 'includes/tripal.bulk_update'),
       );
-      tripal_add_job('Update all aliases for content type: ' . $bundle->label, 'tripal', 
+      tripal_add_job('Update all aliases for content type: ' . $bundle->label, 'tripal',
         'tripal_update_all_urls_and_titles', $args, $user->uid, 10, $includes);
     }
 
@@ -546,12 +574,12 @@ function tripal_admin_add_type_form($form, &$form_state) {
           and return to create new Tripal content types.', TRIPAL_NOTICE);
     return;
   }
-  
+
   // Set the stage to step1 if it isn't already set.
   if (!isset($form_state['stage'])) $form_state['stage'] = 'step1';
   $stage = $form_state['stage'];
-  
-  
+
+
   // Get the selected term.
   if (array_key_exists('values', $form_state) and
       array_key_exists('term', $form_state['values'])) {
@@ -563,14 +591,14 @@ function tripal_admin_add_type_form($form, &$form_state) {
       $selected_term = $selected[0];
     }
   }
-    
+
   // Get the selected storage element.
   $default_store = 'term_chado_storage';
   if (array_key_exists('values', $form_state) and
       array_key_exists('store_select', $form_state['values'])) {
     $default_store = $form_state['values']['store_select'];
   }
-  
+
 
   // Handle the different stages:
   if ($stage == 'step1') {
@@ -585,10 +613,10 @@ function tripal_admin_add_type_form($form, &$form_state) {
     tripal_admin_add_type_form_step2_summary($form, $form_state, $stores, $selected_term, $default_store);
     tripal_admin_add_type_form_step3($form, $form_state, $stores, $selected_term, $default_store);
   }
-  
+
   $form['#prefix'] = '<div id = "tripal-add-type-form">';
   $form['#suffix'] = '</div>';
-  
+
   return $form;
 }
 
@@ -596,7 +624,7 @@ function tripal_admin_add_type_form($form, &$form_state) {
  * Builds step1 of the tripal_admin_add_type_form()
  */
 function tripal_admin_add_type_form_step1(&$form, &$form_state) {
-  
+
   // Get the term name from the form_state.
   $term_name = '';
   if (array_key_exists('values', $form_state) and array_key_exists('term_name0', $form_state['values'])) {
@@ -605,7 +633,7 @@ function tripal_admin_add_type_form_step1(&$form, &$form_state) {
   if (array_key_exists('input', $form_state) and array_key_exists('term_name0', $form_state['input'])) {
     $term_name = $form_state['input']['term_name0'];
   }
-  
+
   // Get the term lookup form.
   $description = t("The content type must be the name of a term in
         a controlled vocabulary and the controlled vocabulary should
@@ -615,7 +643,7 @@ function tripal_admin_add_type_form_step1(&$form, &$form_state) {
   tripal_get_term_lookup_form($form, $form_state, $term_name,
     'Step 1: Content Type', $description, TRUE, '', 0,
     'tripal_admin_add_type_form_ajax_callback');
-  
+
   if ($term_name) {
     $form['term_match']['step1-continue'] = array(
       '#type' => 'submit',
@@ -626,15 +654,15 @@ function tripal_admin_add_type_form_step1(&$form, &$form_state) {
 }
 
 /**
- * Provides a summary of values selected in Step 1. 
+ * Provides a summary of values selected in Step 1.
  */
 function tripal_admin_add_type_form_step1_summary(&$form, &$form_state, $selected_term) {
-     
+
   $form['term'] = [
     '#type' => 'value',
     '#value' => $selected_term,
   ];
-  
+
   $form['term_summary'] = [
     '#type' => 'fieldset',
     '#title' => t('Step 1: Content Type'),
@@ -646,7 +674,7 @@ function tripal_admin_add_type_form_step1_summary(&$form, &$form_state, $selecte
   $form['term_summary']['details'] = [
     '#type' => 'item',
     '#title' => t('Term'),
-    '#markup' => 'Name: ' . $selected_term->name . 
+    '#markup' => 'Name: ' . $selected_term->name .
       '<br>Vocabulary: ' . $selected_term->cv_id->name . ' (' . $selected_term->dbxref_id->db_id->name . ') ' .
       '<br>Term ID: ' . $selected_term->dbxref_id->db_id->name . ':' . $selected_term->dbxref_id->accession . '.  ' .
       '<br>Definition:  ' . $definition
@@ -655,14 +683,14 @@ function tripal_admin_add_type_form_step1_summary(&$form, &$form_state, $selecte
     '#type' => 'submit',
     '#value' => t('Pick a different term'),
     '#name' => 'step1-return',
-  );  
+  );
 }
-  
+
 /**
  * Builds step1 of the tripal_admin_add_type_form()
  */
 function tripal_admin_add_type_form_step2(&$form, &$form_state, $stores, $selected_term, $default_store) {
-  
+
   // Now let the user select where the data type will be stored.
   $form['storage'] = array(
     '#type' => 'fieldset',
@@ -671,7 +699,7 @@ function tripal_admin_add_type_form_step2(&$form, &$form_state, $stores, $select
           must be stored in a single storage backend. Please select the
           storage method and settings for this content type.')
   );
-    
+
   $store_options = array(0 => '-- Select --');
   foreach ($stores as $store_type => $store) {
     $store_options[$store_type] = $store['label'];
@@ -684,7 +712,7 @@ function tripal_admin_add_type_form_step2(&$form, &$form_state, $stores, $select
     '#default_value' => $default_store,
     '#description' => 'Select a storage background for this content type.'
   );
-  
+
   if ($default_store) {
     $form['term_match']['step2-continue'] = array(
       '#type' => 'submit',
@@ -700,12 +728,12 @@ function tripal_admin_add_type_form_step2(&$form, &$form_state, $stores, $select
 function tripal_admin_add_type_form_step2_summary(&$form, &$form_state, $stores, $selected_term, $default_store) {
   $default_store = $form_state['values']['store_select'];
   $selected_store_module = $stores[$default_store]['module'];
-  
+
   $form['store_select'] = [
     '#type' => 'value',
     '#value' => $default_store,
   ];
-  
+
   $form['store_summary'] = [
     '#type' => 'fieldset',
     '#title' => t('Step 2: Storage'),
@@ -722,7 +750,7 @@ function tripal_admin_add_type_form_step2_summary(&$form, &$form_state, $stores,
     '#type' => 'submit',
     '#value' => t('Pick a different term'),
     '#name' => 'step1-return',
-  );  
+  );
 }
 
 /**
@@ -730,9 +758,9 @@ function tripal_admin_add_type_form_step2_summary(&$form, &$form_state, $stores,
  */
 function tripal_admin_add_type_form_step3(&$form, &$form_state, $stores, $selected_term, $default_store) {
   $default_store = $form_state['values']['store_select'];
-  
+
   $selected_store_module = $stores[$default_store]['module'];
-  
+
   $form['store_settings'] = [
     '#type' => 'fieldset',
     '#title' => t('Step 3: Storage Settings'),
@@ -744,7 +772,7 @@ function tripal_admin_add_type_form_step3(&$form, &$form_state, $stores, $select
     $store_form = $function($form, $form_state, $selected_term, $submit_disabled);
     $form['store_settings'][$default_store] = $store_form;
   }
-  
+
   // Add in the button for the cases of no terms or too many.
   $form['submit_button'] = array(
     '#type' => 'submit',
@@ -768,7 +796,7 @@ function tripal_admin_add_type_form_validate($form, &$form_state) {
   $stores = module_invoke_all('vocab_storage_info');
   $store_select = (isset($form_state['values']['store_select'])) ? $form_state['values']['store_select'] : NULL;
   $clicked_button = $form_state['clicked_button']['#name'];
-  
+
   // Don't do validation on an ajax callback.
   if (array_key_exists('#ajax', $form_state['triggering_element'])) {
     return;
@@ -776,8 +804,8 @@ function tripal_admin_add_type_form_validate($form, &$form_state) {
 
   if ($clicked_button =='step1-continue') {
     $form_state['rebuild'] = TRUE;
-    $form_state['stage'] = 'step2';   
-    
+    $form_state['stage'] = 'step2';
+
     $selected = tripal_get_term_lookup_form_result($form, $form_state);
     if (count($selected) == 0) {
       form_set_error('term_match][term_name', 'Please select a vocabulary term.');
@@ -785,23 +813,23 @@ function tripal_admin_add_type_form_validate($form, &$form_state) {
     if (count($selected) > 1) {
       form_set_error('term_match][term_name', 'Please select only one vocabulary term.');
     }
-    
+
   }
   if ($clicked_button =='step1-return') {
     $form_state['rebuild'] = TRUE;
     $form_state['stage'] = 'step1';
   }
   if ($clicked_button =='step2-continue') {
-    
+
     if (!$store_select) {
       form_set_error('store_select', 'Please select only one vocabulary term.');
     }
-    
+
     $form_state['rebuild'] = TRUE;
     $form_state['stage'] = 'step3';
   }
-  
-  if ($clicked_button == 'create-content') {           
+
+  if ($clicked_button == 'create-content') {
     // Call the submit hook for this form for the storage method that
     // will be responsible for this cotent type.
     $stores = module_invoke_all('vocab_storage_info');
@@ -854,7 +882,7 @@ function tripal_admin_add_type_form_submit($form, &$form_state) {
       );
 
       global $user;
-      $job_id = tripal_add_job("Create content type: " . $term_name . ' ('. $vocabulary . ':' . $accession . ')', 
+      $job_id = tripal_add_job("Create content type: " . $term_name . ' ('. $vocabulary . ':' . $accession . ')',
         'tripal', 'tripal_create_bundle', [$args], $user->uid);
 
       if (!$job_id) {
@@ -888,14 +916,14 @@ function tripal_admin_access($entity) {
   $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
 
   if (!$bundle) {
-    tripal_report_error('tripal', TRIPAL_WARNING, 
+    tripal_report_error('tripal', TRIPAL_WARNING,
       'Unable to load bundle :name when creating permissions.', array(':name' => $bundle_name));
     return FALSE;
   }
 
   // Get the administrative user roles.
   $admin_role = NULL;
-  $admin_rid = variable_get('user_admin_role'); 
+  $admin_rid = variable_get('user_admin_role');
   if (!$admin_rid) {
     // If we couldn't identify a single role from the 'user_admin_role' variable
     // then let's get the role that is currently set to administer tripal. If
@@ -910,13 +938,13 @@ function tripal_admin_access($entity) {
       $admin_rid = 3;
     }
   }
-  
+
   // If we can't find a unique admin role then just don't add one and
   // the user will be forced to manually set permissions for the admin.
   if (!$admin_rid) {
     return FALSE;
   }
-  
+
   // Define the permissions.
   $permission_for_role = array(
     'create ' . $bundle->name => TRUE,
@@ -924,7 +952,7 @@ function tripal_admin_access($entity) {
     'edit ' . $bundle->name => TRUE,
     'delete ' . $bundle->name => TRUE,
   );
-  
+
   // Assign the permissions
   user_role_change_permissions($admin_rid, $permission_for_role);
 

+ 6 - 5
tripal/includes/TripalEntityController.inc

@@ -636,6 +636,7 @@ class TripalEntityController extends EntityAPIController {
         $info = entity_get_info($queried_entities[$id]->type);
         $field_cache = array_key_exists('field cache', $info) ? $info['field cache'] : FALSE;
         $bundle_name = $queried_entities[$id]->bundle;
+        $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
 
         // Iterate through the field instances and find those that are set to
         // 'auto_attach' and which are attached to this bundle. Add all
@@ -687,13 +688,13 @@ class TripalEntityController extends EntityAPIController {
           // attach then we will ignore it. It can only be set by providing
           // the id in the $field_id array handled previously.
           else {
-            // We only load via AJAX if empty fields are not hidden.
-            $bundle = tripal_load_bundle_entity(array('name' => $bundle_name));
-            $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
+            
+            // Do not load fields that are not auto attached.  Instead set
+            // their value to an empty string and set the #processed key to
+            // FALSE.
             if (array_key_exists('settings', $instance) and
                 array_key_exists('auto_attach', $instance['settings']) and
-                $instance['settings']['auto_attach'] == FALSE and
-                $hide_variable == 'show') {
+                $instance['settings']['auto_attach'] == FALSE) {
 
                // Add an empty value. This will allow the tripal_entity_view()
                // hook to add the necessary prefixes to the field for ajax

+ 46 - 16
tripal/includes/TripalEntityUIController.inc

@@ -91,6 +91,14 @@ class TripalEntityUIController extends EntityDefaultUIController {
       'type' => MENU_LOCAL_TASK,
       'weight' => -8,
     );
+    $items['bio_data/' . $wildcard . '/reload'] = array(
+      'title'  => 'Reload',
+      'page callback' => 'tripal_entity_reload',
+      'page arguments' => array(1),
+      'access arguments' => array('administer tripal'),
+      'type' => MENU_LOCAL_TASK,
+      'weight' => 10,
+    );
     // Menu item for deleting tripal data entities.
     $items['bio_data/' . $wildcard . '/delete'] = array(
       'title'  => 'Delete',
@@ -454,6 +462,28 @@ function tripal_view_entity($entity, $view_mode = 'full') {
    }
  }
 
+/**
+ * Clears the cache of a given entity to force a reload.
+ *
+ * @param $entity_id
+ *   The entity_id of the entity to reload.
+ */
+function tripal_entity_reload($entity) {
+  $entity_id = $entity->id;
+  $cid = 'field:TripalEntity:' . $entity_id . ':';
+  cache_clear_all($cid, 'cache_field', TRUE);
+
+  $sql = "SELECT count(*) FROM cache_field WHERE cid like :cid";
+  $count = db_query($sql, [':cid' => $cid . '%'])->fetchField();
+  if (!isset($count) or $count > 0) {
+    drupal_set_message('Failed to clear the cache for this entity.');
+  }
+  else {
+    drupal_set_message('Cache cleared, entity reloaded');
+  }
+  drupal_goto('bio_data/' . $entity_id);
+}
+
  /**
   *
   */
@@ -701,19 +731,19 @@ function tripal_entity_form_submit($form, &$form_state) {
  * TripalEntityUIController class.
  */
 function tripal_add_page() {
-  
+
   // The content array to be returned.
   $content = [];
-   
+
   $content['instructions'] = [
     '#type' => 'markup',
-    '#markup' => 'This page provides links for creating pages for Tripal ' . 
-      'supported content types. These content types are organized by categories. ' . 
-      'Please note, however, that the categorization is the most common use ' . 
+    '#markup' => 'This page provides links for creating pages for Tripal ' .
+      'supported content types. These content types are organized by categories. ' .
+      'Please note, however, that the categorization is the most common use ' .
       'for a given type. Some types may be useful in other "categories" as well.',
     '#weight' => -15,
   ];
-  
+
 
   // Get the list of categories.
   $select = "
@@ -723,8 +753,8 @@ function tripal_add_page() {
     WHERE TV.name = 'bundle_category'
   ";
   $categories = db_query($select);
-  
-  
+
+
   // Build the fieldsets for the categories.
   $fieldsets = [];
   $category_weight = 1;
@@ -744,7 +774,7 @@ function tripal_add_page() {
       '#weight' => $category == 'General' ? -10 : $category_weight++,
     ];
   }
-  
+
   // Create the "other" fieldset, and set it's weight to 100 so it goes to
   // the bottom.
   $fieldsets['Other_fieldset'] = [
@@ -759,14 +789,14 @@ function tripal_add_page() {
     '#attached' => array(
       'js' => array('misc/collapse.js', 'misc/form.js')
     ),
-  ]; 
-  
-  
+  ];
+
+
   // Get the list of bundles and iterate through them.
   $select = "SELECT id, name, label FROM tripal_bundle ORDER BY label";
   $bundles = db_query($select);
   while ($bundle = $bundles->fetchObject()) {
-    
+
     // Lookup the bundle category.
     $sql = "
       SELECT TBV.value as category
@@ -775,12 +805,12 @@ function tripal_add_page() {
         INNER JOIN tripal_variables TV on TV.variable_id = TBV.variable_id
       WHERE TV.name = 'bundle_category' and TB.id = :id;
     ";
-  
+
     $category = db_query($sql, [':id' => $bundle->id])->fetchField();
     if (!$category) {
       $category = 'Other';
     }
-    
+
     $machine_name = preg_replace('/[^\w]/', '_', $category);
     $bundle = tripal_load_bundle_entity(['id' => $bundle->id]);
     if (!$bundle) {
@@ -797,7 +827,7 @@ function tripal_add_page() {
       ];
     }
   }
-  
+
   // Now iterate through the fieldsets and set their weight
   return $content;
 }

+ 8 - 0
tripal/includes/TripalFields/TripalFieldWidget.inc

@@ -116,10 +116,18 @@ class TripalFieldWidget {
    * Performs validation of the widget form.
    *
    * Use this validate to ensure that form values are entered correctly.
+   *
    * The 'value' key of this field must be set in the $form_state['values']
    * array anytime data is entered by the user.  It may be the case that there
    * are other fields for helping select a value. In the end those helper
    * fields must be used to set the 'value' field.
+   *
+   * TROUBLESHOOTING:
+   *  If your widget doesn't appear to be saving data, check the following:
+   *   - Is $form_state['values'] set to a TRUE value (e.g. a string)?
+   *   - Do you have the keys needed by the storage backend? For example,
+   *     ChadoFields need `chado-[tablename]__[columnname]` keys for all
+   *     chado columns. Look at the storage backend for more documentation.
    */
   public function validate($element, $form, &$form_state, $langcode, $delta) {
 

+ 100 - 36
tripal/includes/tripal.entity.inc

@@ -496,50 +496,112 @@ function tripal_entity_access($op, $entity = NULL, $account = NULL, $entity_type
   return FALSE;
 }
 
+/**
+ * Implements hook_entity_prepare_view
+ * 
+ * This function is called before building the content array for an entity. It
+ * allows us to load any unattached fields if AJAX is turned off.
+ */
+function tripal_entity_prepare_view($entities, $type, $langcode) {
+  
+  // This is for only TripalEntity content types.
+  if ($type != 'TripalEntity') {
+    return;
+  }
+
+  // Iterate through the entities and instancesa and if AJAX loading is turned
+  // off then we need to load those fields that were not auto attached.
+  foreach ($entities as $entity_id => &$entity) {
+    $bundle = tripal_load_bundle_entity(['name' => $entity->bundle]);
+    $use_ajax = tripal_get_bundle_variable('ajax_field', $bundle->id);
+    $instances = field_info_instances('TripalEntity', $entity->bundle);
+    foreach ($instances as $instance) {
+      $field_name = $instance['field_name'];
+      $field = field_info_field($field_name);
+      
+      $auto_attach = array_key_exists('auto_attach', $instance['settings']) ? $instance['settings']['auto_attach'] : TRUE;
+      $processed = $entity->{$field_name}['#processed'];
+      
+      // If the field is not ajax loadable and the field is not auto attached
+      // then we need to add the value.
+      if (!$use_ajax and $auto_attach == FALSE and $processed == FALSE) {
+        $temp = tripal_load_entity('TripalEntity', [$entity_id], TRUE, [$field['id']]);
+        reset($temp);
+        $entity->{$field_name} = $temp[$entity_id]->{$field_name};
+        $items = field_get_items('TripalEntity', $entity, $field_name);        
+      }
+    }
+  }
+}
 
 /**
  * Implements hook_entity_view.
  *
  * Here we want to overwite unattached fields with a div box that will be
  * recognized by JavaScript that will then use AJAX to load the field.
- *
  * The tripal_ajax_attach_field() function is called by an AJAX call to
- * retrieve the field.
+ * retrieve the field.  We also remove empty fields that were auto attached.
  */
 function tripal_entity_view($entity, $type, $view_mode, $langcode) {
 
-  if ($type == 'TripalEntity') {
-
-    foreach (element_children($entity->content) as $child_name) {
-
-      // Initailize the prefix and suffix for this field.
-      if (!array_key_exists('#prefix', $entity->content[$child_name])) {
-        $entity->content[$child_name]['#prefix'] = '';
-      }
-      if (!array_key_exists('#suffix', $entity->content[$child_name])) {
-        $entity->content[$child_name]['#suffix'] = '';
-      }
-
-      // Surround the field with a <div> box for AJAX loading if this
-      // field is unattached.  this will allow JS code to automatically load
-      // the field.
-      $instance = field_info_instance('TripalEntity', $child_name, $entity->bundle);
-      if ($instance and array_key_exists('settings', $instance)) {
-        $class = '';
-        if (array_key_exists('auto_attach', $instance['settings']) and
-            $instance['settings']['auto_attach'] == FALSE and
-            $entity->{$child_name}['#processed'] == FALSE) {
-          // If the field is empty then try to use ajax to load it.
-          $items = field_get_items('TripalEntity', $entity, $child_name);
-          if (count($items) == 0 or empty($items[0]['value'])) {
-            $class = 'class="tripal-entity-unattached"';
-          }
-        }
-        $entity->content[$child_name]['#prefix'] .= '<div id="tripal-entity-' . $entity->id . '--' . $child_name . '" ' . $class . '>';
-        $entity->content[$child_name]['#suffix'] .= '</div>';
+  // This is for only TripalEntity content types.
+  if ($type != 'TripalEntity') {
+    return;
+  }
+    
+  $bundle = tripal_load_bundle_entity(['name' => $entity->bundle]);
+  $hide_empty = tripal_get_bundle_variable('hide_empty_field', $bundle->id);
+  $use_ajax = tripal_get_bundle_variable('ajax_field', $bundle->id);
+
+  // Iterate through the fields attached to this entity and add IDs to them
+  // as well as some classe for ajax loading.
+  foreach (element_children($entity->content) as $child_name) {
+
+    // Surround the field with a <div> box for AJAX loading if this
+    // field is unattached.  this will allow JS code to automatically load
+    // the field.
+    $instance = field_info_instance('TripalEntity', $child_name, $entity->bundle);
+    if (!$instance) {
+      continue;
+    }
+    $field = field_info_field($instance['field_name']);
+     
+    // Check if this is an AJAX loadable field, and if so set the class.
+    $class = '';
+    $auto_attach = array_key_exists('auto_attach', $instance['settings']) ? $instance['settings']['auto_attach'] : TRUE;
+    $processed = $entity->{$child_name}['#processed'];
+    if ($use_ajax and $auto_attach == FALSE and $processed == FALSE) {
+      $class = 'class="tripal-entity-unattached"';
+    }
+    
+    // Set the prefix and suffix.
+    if (!array_key_exists('#prefix', $entity->content[$child_name])) {
+      $entity->content[$child_name]['#prefix'] = '';
+    }
+    if (!array_key_exists('#suffix', $entity->content[$child_name])) {
+      $entity->content[$child_name]['#suffix'] = '';
+    }
+    $entity->content[$child_name]['#prefix'] .= '<div id="tripal-entity-' . $entity->id . '--' . $child_name . '" ' . $class . '>';
+    $entity->content[$child_name]['#suffix'] .= '</div>';
+    
+    // Remove any auto attached fields if they are empty.
+    if ($hide_empty and $processed) {
+      $items = field_get_items('TripalEntity', $entity, $child_name);
+      if (tripal_field_is_empty($field, $items)) {
+        unset($entity->content[$child_name]);
       }
     }
   }
+  
+  //dpm($entity->content);
+  // Add some settings for AJAX to deal with fields.
+  $settings = [
+    'tripal_display' => [
+      'hide_empty' => $hide_empty,
+      'use_ajax' => $use_ajax
+    ]
+  ];
+  drupal_add_js($settings, 'setting');
 }
 
 /**
@@ -551,12 +613,12 @@ function tripal_entity_view($entity, $type, $view_mode, $langcode) {
  */
 function tripal_ajax_attach_field($id) {
 
-  $matches = array();
+  $matches = [];
   if (preg_match('/^tripal-entity-(\d+)--(.+)$/', $id, $matches)) {
     $entity_id = $matches[1];
     $field_name = $matches[2];
     $field = field_info_field($field_name);
-    $result = tripal_load_entity('TripalEntity', array($entity_id), FALSE, array($field['id']));
+    $result = tripal_load_entity('TripalEntity', [$entity_id], FALSE, [$field['id']]);
     reset($result);
     $entity = $result[$entity_id];
 
@@ -566,13 +628,15 @@ function tripal_ajax_attach_field($id) {
     // instance default display settings. Not sure why it does this. It needs
     // more investigation.
     $instance = field_info_instance('TripalEntity', $field_name, $entity->bundle);
+    $items = field_get_items('TripalEntity', $entity, $field_name);
     $element = field_view_field('TripalEntity', $entity, $field_name, $instance['display']['default']);
     $element['#label_display'] = 'hidden';
 
     $content = drupal_render($element);
-    return drupal_json_output(array(
+    return drupal_json_output([
       'id' => $id,
-      'content' => $content
-    ));
+      'content' => $content,
+      'is_empty' => tripal_field_is_empty($field, $items),
+    ]);
   }
 }

+ 0 - 26
tripal/includes/tripal.fields.inc

@@ -913,32 +913,6 @@ function tripal_form_field_ui_display_overview_form_alter(&$form, &$form_state,
   }
 }
 
-/**
- * Implements hook_field_is_empty().
- */
-function tripal_field_is_empty($item, $field) {
-
-  // If the $item argument is empty then return TRUE.
-  if (!$item) {
-    return TRUE;
-  }
-
-  // If the field is a tripal field storage API field and there 
-  // is no value field then the field is empty.
-  if (array_key_exists('tripal_storage_api', $field['storage']['settings']) and !array_key_exists('value', $item)) {
-    return TRUE;
-  }
-
-  // If there is a value field but there's nothing in it, the the field is
-  // empty.
-  if (array_key_exists('value', $item) and empty($item['value'])) {
-    return TRUE;
-  }
-
-  // Otherwise, the field is not empty.
-  return FALSE;
-}
-
 /**
  * Theme function for all TripalFieldWidget objects.
  *

+ 202 - 21
tripal/theme/js/tripal.js

@@ -1,27 +1,208 @@
-// Using the closure to map jQuery to $. 
+// Using the closure to map jQuery to $.
 (function ($) {
   // Store our function as a property of Drupal.behaviors.
   Drupal.behaviors.tripal = {
     attach: function (context, settings) {
+    	
+      // If we don't have any settings, this is not a entity page so exit
+      if (!settings.tripal_display) {
+        return;
+      }
 
-      $(".tripal-entity-unattached .field-items").replaceWith('<div class="field-items">Loading... <img src="' + tripal_path + '/theme/images/ajax-loader.gif"></div>');
-      $(".tripal-entity-unattached").each(function() {
-        id = $(this).attr('id');
-        if (id) {
-          $.ajax({
-            url: baseurl + '/bio_data/ajax/field_attach/' + id,
-            dataType: 'json',
-            type: 'GET',
-            success: function(data){
-              var content = data['content'];
-              var id = data['id'];
-              $("#" + id + ' .field-items').replaceWith(content);
-            }
-          });
-        }
+      // If the site does not support AJAX loading of fields then we're done.
+      // Tripal will have already removed empty fields that wouldn't have 
+      // needed AJAX loading.
+      var use_ajax = settings.tripal_display.use_ajax;
+      if (!use_ajax) {
+        return;
+      }
+
+      // Attach all fields that require AJAX loading. 
+      $('.tripal-entity-unattached .field-items').replaceWith('<div class="field-items">Loading... <img src="' + tripal_path + '/theme/images/ajax-loader.gif"></div>');
+      $('.tripal-entity-unattached').each(function () {
+        var id = $(this).attr('id');
+        var hide_empty_field = settings.tripal_display.hide_empty;
+        var field = new TripalAjaxField(id, hide_empty_field);
+        field.attach();
       });
     }
+  /*    else {
+  if (pane_id) {
+    $('#' + pane_id).show(0);
+  }
+*/
+  };
+
+  /* ------------------------------------------------------------------------
+   *                        TripalPane Class
+   * ------------------------------------------------------------------------
+   */
+  
+  /**
+   * TripalPane constructor
+   */
+  function TripalPane(id, hidden) {
+    this.id = id;
+    this.hidden = hidden;
+  }
+  
+  /**
+   * Indicates if the pane has any fields as chidren.
+   */
+  TripalPane.prototype.hasChildren = function() {
+	var num_children = $('.tripal_pane-fieldset-' + this.id)
+	  .first()
+	  .children()
+	  .not('.tripal_pane-fieldset-buttons')
+	  .not('.field-group-format-title')
+	  .not('#' + this.id)
+	  .length > 0;
+	  
+	if (num_children > 0) {
+      return true;
+	}
+	return false;
+  }
+  
+  /**
+   * Removes the pane from the HTML of the page.
+   */
+  TripalPane.prototype.remove = function() {
+	// Remove the Pane's fieldset
+	var pane = $('.tripal_pane-fieldset-' + this.id);
+    pane.remove();
+    
+    // Remove the pane's title from the TOC.
+    $('#' + this.id).parents('.views-row').remove();
   }
+  
+  /**
+   * Removes a child from the pane.
+   */
+  TripalPane.prototype.removeChild = function(child_id) {
+    var child = $('#' + child_id);
+    
+    // If this child is within a table then remove the row.
+    var row = child.parents('tr');
+    if (row) {
+      row.remove();
+    }
+    
+    child.remove();
+  }
+  
+  /* ------------------------------------------------------------------------
+   *                        TripalAjaxField Class
+   * ------------------------------------------------------------------------
+   */
+  
+  /**
+   * TripalAjaxField Constructor.
+   *
+   * @param {Number} id
+   * @param {Boolean} hide_fields
+   * @constructor
+   */
+  function TripalAjaxField(id, hide_empty_field) {
+    this.id = id;
+    this.hide_empty_field = hide_empty_field;
+    
+    // Get the pane that this field beongs to (if one exists).
+    this.pane = this.getPane(); 
+  }
+
+  /**
+   * Load the field's content from the server.
+   */
+  TripalAjaxField.prototype.attach = function () {
+    $.ajax({
+      url     : baseurl + '/bio_data/ajax/field_attach/' + this.id,
+      dataType: 'json',
+      type    : 'GET',
+      success : this.setFieldContent.bind(this)
+    });
+  };
+
+  /**
+   * Add the content of the field to its pane.
+   *
+   * @param data
+   */
+  TripalAjaxField.prototype.setFieldContent = function (data) {
+	// Get the data items: the content, if this field is empty and the id 
+	// of this field.
+    var content = data['content'];
+    var empty = data['is_empty'];
+    var id = data['id'];
+    
+    // Get the field object.
+    var field = $('#' + id);
+    
+    // First step, set the content for this field.  This will be the
+    // field formatter content.
+    $('#' + id + ' .field-items').replaceWith(content);
+
+    // If the field is not empty then we're done.  Always show non-empty fields.
+    if (!empty) {
+      return;
+    }
+    
+    // If empty fields should not be hidden then return.
+    if (!this.hide_empty_field) {
+      return;
+    }
+	
+	// Second, if this field is part of a pane then we need to remove it
+	// from the pane. Otherwise, just remove it.
+	if (this.pane) {
+
+      // Remove this field from the pane.
+      this.pane.removeChild(id);
+  
+      // If the pane has no more children then remove it.
+      if (!this.pane.hasChildren()) {
+        this.pane.remove();
+      }
+	}
+	else {
+	  field.remove();
+	}
+  };
+
+  /**
+   * Extract the pane id from parent classes.
+   *
+   * @param classes
+   * @return {String|null}
+   */
+  TripalAjaxField.prototype.getPane = function () {
+	  
+	// Get the pane for this field.
+	var field = $('#' + this.id);
+	var pane = field.parents('.tripal_pane')
+	
+	// If the field is not in a pane then just return.
+	if (pane.length == 0) {
+	  return null;
+	}
+	
+	// Get further details about the pane.
+	var classes = pane.first().attr('class').split(' ');
+    var sub_length = 'tripal_pane-fieldset-'.length;
+    var pane_id = null;
+
+    classes.map(function (cls) {
+      if (cls.indexOf('tripal_pane-fieldset-') > -1) {
+        pane_id = cls.substring(sub_length, cls.length);
+      }
+    });
+    
+    if (pane_id) {
+      var pane = new TripalPane(pane_id, false);
+      return pane;
+    }
+    return null;
+  };
 
 })(jQuery);
 
@@ -34,11 +215,11 @@ function tripal_navigate_field_pager(id, page) {
   });
 
   jQuery.ajax({
-    type: "GET",
-    url: Drupal.settings["basePath"] + "bio_data/ajax/field_attach/" + id,
-    data: { 'page' : page },
-    success: function(response) {
-      jQuery("#" + id + ' .field-items').replaceWith(response['content']);
+    type   : 'GET',
+    url    : Drupal.settings['basePath'] + 'bio_data/ajax/field_attach/' + id,
+    data   : {'page': page},
+    success: function (response) {
+      jQuery('#' + id + ' .field-items').replaceWith(response['content']);
     }
   });
 }

+ 22 - 0
tripal/tripal.install

@@ -50,6 +50,10 @@ function tripal_add_variables() {
     'hide_empty_field',
     'Structure->Tripal Content Type->edit checkbox to hide empty fields for that bundle.'
   );
+  tripal_insert_variable(
+    'ajax_field',
+    'Structure->Tripal Content Type->edit checkbox for ajax fields for that bundle.'
+  );
 }
 
 /**
@@ -1209,3 +1213,21 @@ function tripal_update_7312() {
   }
 }
 
+
+/**
+ * Adds a tripal_storage_api setting to all field storage details
+ */
+function tripal_update_7313() {
+  $transaction = db_transaction();
+  try {
+    tripal_insert_variable(
+      'ajax_field',
+      'Structure->Tripal Content Type->edit checkbox ajax fields for that bundle.'
+    );
+  } catch (\PDOException $e) {
+    $transaction->rollback();
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: ' . $error);
+  }
+}
+

+ 38 - 40
tripal/tripal.module

@@ -333,15 +333,15 @@ function tripal_menu() {
       if (!$callback) {
         $callback = 'drupal_get_form';
         $page_args = ['tripal_get_importer_form', $class_name];
-      }      
+      }
       if (!$callback_path) {
         $callback_path = 'includes/tripal.importer.inc';
       }
       $file_path = drupal_get_path('module', 'tripal');
       if ($callback_path and $callback_module) {
         $file_path = drupal_get_path('module', $callback_module);
-      }      
-      
+      }
+
       $items[$menu_path] = array(
         'title' => $class_name::$name,
         'description' =>  $class_name::$description,
@@ -403,8 +403,8 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   );
-  
-  
+
+
   //
   // USER FILE MANAGEMENT
   //
@@ -419,7 +419,7 @@ function tripal_menu() {
     'file path' => drupal_get_path('module', 'tripal'),
     'weight' => 30,
   ];
-  
+
   $items['admin/tripal/files/quota'] = [
     'title' => 'User Quotas',
     'description' => 'Set default quota, expiration date, and custom quotas',
@@ -431,7 +431,7 @@ function tripal_menu() {
     'file path' => drupal_get_path('module', 'tripal'),
     'weight' => 10,
   ];
-  
+
   // Admin remove user quota
   $items['admin/tripal/files/quota/remove/%'] = [
     'title' => 'Remove custom user quota',
@@ -443,7 +443,7 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin_files.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
-  
+
   // Add user quota
   $items['admin/tripal/files/quota/add'] = [
     'title' => 'Add Custom Quota',
@@ -457,7 +457,7 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin_files.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
-  
+
   // Autocomplete path for the users on the site
   $items['admin/tripal/files/quota/user/autocomplete'] = [
     'title' => 'Autocomplete for existing users',
@@ -468,7 +468,7 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin_files.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
-  
+
   // Edit user quota
   $items['admin/tripal/files/quota/edit/%'] = [
     'title' => 'Edit Custom Quota',
@@ -480,7 +480,7 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin_files.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
-  
+
   $items['admin/tripal/files/usage'] = [
     'title' => 'File Usage',
     'description' => 'Set default quota, expiration date, and custom quotas',
@@ -493,11 +493,11 @@ function tripal_menu() {
     'weight' => 15
   ];
 
-  
+
   //
   // USER FILES
   //
-  
+
   // User view quota (Tab)
   $items['user/%/files'] = [
     'title' => 'Files',
@@ -511,7 +511,7 @@ function tripal_menu() {
     'file path' => drupal_get_path('module', 'tripal'),
     'weight' => 10,
   ];
-  
+
   $items['user/%/files/%'] = [
     'title' => 'File Details',
     'description' => "View details about the file",
@@ -523,7 +523,7 @@ function tripal_menu() {
     'file' => 'includes/tripal.user.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
-  
+
   // User file renew.
   $items['user/%/files/%/renew'] = [
     'title' => 'Renew File',
@@ -567,7 +567,7 @@ function tripal_menu() {
  * Checks if the current user has permissions to perform an action on a file.
  *
  * @param $op
- *   The operation to perform.  These include 'view', 'download', 'renew' and 
+ *   The operation to perform.  These include 'view', 'download', 'renew' and
  *   'delete'
  * @param $uid
  *   The user ID of the user's account that owns the file.
@@ -576,23 +576,23 @@ function tripal_menu() {
  */
 function tripal_access_user_files($op, $uid, $fid = NULL) {
   global $user;
-  
+
   // The site admin can do anything.
   if (in_array('administrator', $user->roles)) {
     return TRUE;
   }
-  
+
   // Only the user that owns the files can see them.
   if ($uid != $user->uid) {
     return FALSE;
   }
-  
+
   // If no file ID is provided and the user wants to view then
   // this is the case where the user wants to see all the files.
   if (!$fid and $op == 'view') {
     return TRUE;
   }
-    
+
   $file = file_load($fid);
   switch ($op) {
     case 'view':
@@ -608,7 +608,7 @@ function tripal_access_user_files($op, $uid, $fid = NULL) {
 }
 /**
  * An access function for data collections.
- * 
+ *
  * @return boolean
  */
 function tripal_accesss_user_collections($uid) {
@@ -633,7 +633,7 @@ function tripal_users_autocomplete($string) {
     ->fields('u', ['name'])
     ->condition('name', '%' . db_like($string) . '%', 'LIKE')
     ->execute();
-  
+
   foreach ($result as $row) {
     $matches[$row->name] = check_plain($row->name);
   }
@@ -722,7 +722,7 @@ function tripal_permission() {
       'restrict access' => TRUE,
     );
   }
-  
+
   // Add permissions for each Importer
   $importers = tripal_get_importers();
   foreach ($importers as $class_name) {
@@ -1333,7 +1333,7 @@ function tripal_cron() {
   // Check for expired collections.
   tripal_add_job('Cron: Checking expired collections', 'tripal',
     'tripal_expire_collections', $args, 1, 1, $includes, TRUE);
-  
+
   tripal_add_job('Cron: Checking expired files', 'tripal',
     'tripal_expire_files', $args, 1, 1, $includes, TRUE);
 
@@ -1483,7 +1483,7 @@ function tripal_html5_file_validate($element, &$form_state) {
   $name = $element['#name'];
   $name = preg_replace('/[^\w]/', '_', $name);
   $fid = NULL;
-  if (is_array($element['#value']) and $array_key_exists($name, $element['#value'])) {
+  if (is_array($element['#value']) and array_key_exists($name, $element['#value'])) {
     $fid = $element['#value'][$name];
   }
 
@@ -1518,23 +1518,21 @@ function tripal_html5_file_value($element, $input = FALSE, &$form_state) {
  * @param $context
  */
 function tripal_field_display_TripalEntity_alter(&$display, $context){
+/*   $field = $context['field'];
   $field_name = $context['field']['field_name'];
   $bundle = $context['entity']->bundle;
   $bundle_info = tripal_load_bundle_entity(array('name' => $bundle));
 
   // Hide fields that are empty, but only if the hide_empty_field variable
-  // is set to 'hide' for this bundel.
-  $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id, 'hide');
-  if($hide_variable == 'hide'){
-    $item = field_get_items('TripalEntity', $context['entity'], $field_name);
-    if($item) {
-      $field = field_info_field($field_name);
-      if(tripal_field_is_empty($item[0], $field)) {
-        // Stop the right rail element from rendering.
-        drupal_add_css('.' . $field_name.' {display: none;}', 'inline');
-      }
+  // is set to TRUE for this bundle and ajax load is turned off.
+  $hide_empty = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id);
+  if ($hide_empty == TRUE) {
+    $items = field_get_items('TripalEntity', $context['entity'], $field_name);
+    if (tripal_field_is_empty($field, $items)) {
+      // Stop the right rail element from rendering.
+      drupal_add_css('.' . $field_name.' {display: none;}', 'inline');
     }
-  }
+  }  */
 }
 
 /**
@@ -1555,10 +1553,10 @@ function tripal_field_group_table_rows_alter(&$element, &$children) {
       $bundle = $element[$child]['#bundle'];
       $bundle_info = tripal_load_bundle_entity(array('name' => $bundle));
 
-      // If the hide empty variable is turned on then remove fields from
-      // the field group.
-      $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id, 'hide');
-      if($hide_variable == 'hide'){
+      // If the hide empty variable is turned on and ajax load is turned off
+      // then remove fields from the field group.
+      $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id);
+      if($hide_variable == TRUE){
         $items = $element[$child]['#items'];
         // Case #1: there are no items.
         if (count($items) == 0) {

+ 29 - 33
tripal_chado/api/tripal_chado.api.inc

@@ -27,7 +27,7 @@
  *   A key/value associative array that supports the following keys:
  *   - bundle_name:  The name of the the TripalBundle (e.g. bio_data-12345).
  * @param $job
- *   The jobs management object for the job if this function is run as a job. 
+ *   The jobs management object for the job if this function is run as a job.
  *   This argument is added by Tripal during a job run and is not needed if
  *   this function is run directly.
  *
@@ -38,7 +38,7 @@
  * @ingroup tripal_chado_api
  */
 function chado_publish_records($values, $job = NULL) {
- 
+
   // Used for adding runtime to the progress report.
   $started_at = microtime(true);
 
@@ -48,7 +48,11 @@ function chado_publish_records($values, $job = NULL) {
     $job = new TripalJob();
     $job->load($job_id);
   }
-  
+  $report_progress = TRUE;
+  if (!is_object($job)) {
+    $report_progress = FALSE;
+  }
+
   // These are options for the tripal_report_error function. We do not
   // want to log messages to the watchdog but we do for the job and to
   // the terminal
@@ -206,40 +210,29 @@ function chado_publish_records($values, $job = NULL) {
   $sql = "SELECT count(*) as num_records " . $from . $where;
   $result = chado_query($sql, $args);
   $count = $result->fetchField();
-  
+
   tripal_report_error($message_type, TRIPAL_INFO,
     "There are !count records to publish.",
     ['!count' => $count], $message_opts);
+  
+  if ($report_progress) {
+    $job->setTotalItems($count); 
+    $job->setItemsHandled(0);
+    $job->setInterval(1);
+  }
 
-  // Perform the query.
-  $sql = $select . $from . $where . ' LIMIT '.$chunk_size;
+  // Perform the query in chunks.
+  $sql = $select . $from . $where . ' LIMIT '. $chunk_size;
   $more_records_to_publish = TRUE;
-  $total_published = 0;
   while ($more_records_to_publish) {
 
     $records = chado_query($sql, $args);
 
-    // Update the job status every chunk start.
-    // Because this is outside of hte transaction, we can update the admin through the jobs UI.
-    $complete = 0;
-    if ($count > 0) {
-      $complete = ($total_published / $count) * 33.33333333;
-    }
-    if ($report_progress) { $job->setProgress(intval($complete * 3)); }
-    if ($total_published === 0) {
-      printf("%d of %d records. (%0.2f%%) Memory: %s bytes.\r",
-        $i, $count, 0, number_format(memory_get_usage()), 0);
-    }
-    else {
-      printf("%d of %d records. (%0.2f%%) Memory: %s bytes; Current run time: %s minutes.\r",
-        $total_published, $count, $complete * 3, number_format(memory_get_usage()), number_format((microtime(true) - $started_at)/60, 2));
-    }
-
-    // There is no need to cache transactions since Drupal handles nested 
-    // transactions "by performing no transactional operations (as far as the 
-    // database sees) within the inner nesting layers". Effectively, Drupal 
-    // ensures nested trasactions work the same as passing a transaction 
-    // through to the deepest level and not starting a new transaction if we 
+    // There is no need to cache transactions since Drupal handles nested
+    // transactions "by performing no transactional operations (as far as the
+    // database sees) within the inner nesting layers". Effectively, Drupal
+    // ensures nested trasactions work the same as passing a transaction
+    // through to the deepest level and not starting a new transaction if we
     // are already in one.
     $transaction = db_transaction();
     try {
@@ -288,7 +281,9 @@ function chado_publish_records($values, $job = NULL) {
         }
 
         $i++;
-        $total_published++;
+        if ($report_progress) {
+          $job->setItemsHandled($i);
+        }
       }
     }
     catch (Exception $e) {
@@ -299,7 +294,8 @@ function chado_publish_records($values, $job = NULL) {
       return FALSE;
     }
 
-    // If we get through the loop and haven't completed 100 records, then we're done!
+    // If we get through the loop and haven't completed 100 records, then 
+    // we're done!
     if ($i < $chunk_size) {
       $more_records_to_publish = FALSE;
     }
@@ -309,9 +305,9 @@ function chado_publish_records($values, $job = NULL) {
   }
 
   tripal_report_error($message_type, TRIPAL_INFO,
-    "Succesfully published %count %type record(s).",
-    ['%count' => $total_published, '%type' => $bundle->label], $message_opts);
-  
+    "Successfully published !count !type record(s).",
+    ['!count' => $i, '!type' => $bundle->label], $message_opts);
+
   return TRUE;
 }
 

+ 2 - 1
tripal_chado/includes/TripalFields/ogi__location_on_map/ogi__location_on_map.inc

@@ -322,10 +322,11 @@ class ogi__location_on_map extends ChadoField {
       }
     }
 
+
     // If there are no map positions expanded above then remove the stub.
     // This is needed to ensure this field isn't displayed when there are no locations.
     if (!isset($feature->featurepos->feature_id) OR (sizeof($feature->featurepos->feature_id) == 0)) {
-      unset($entity->{$field_name});
+      $entity->{$field_name}['und'][0]['value'] = array();
     }
   }
 

+ 0 - 6
tripal_chado/includes/TripalFields/schema__publication/schema__publication.inc

@@ -144,12 +144,6 @@ class schema__publication extends ChadoField {
       }
     }
 
-    // Ensure we don't have a value if there are no publications.
-    // This is needed due to stubbing out the field above.
-    if (sizeof($pubs) == 0) {
-      unset($entity->{$field_name});
-    }
-
     $i = 0;
     foreach ($pubs as $pub_id => $pub) {
       $pub_details = chado_get_minimal_pub_info($pub);

+ 0 - 6
tripal_chado/includes/TripalFields/sio__annotation/sio__annotation.inc

@@ -328,12 +328,6 @@ class sio__annotation extends ChadoField {
         $entity->{$field_name}['und'][$i]['chado-' . $field_table . '__pub_id'] = $linker->pub_id;
       }
     }
-
-    // If there are no cvterms selected above then remove the stub.
-    // This is needed to ensure this field isn't displayed when there are no annotations.
-    if (sizeof($fcvterms) == 0) {
-      unset($entity->{$field_name});
-    }
   }
 }
 

+ 5 - 2
tripal_chado/includes/TripalImporter/OBOImporter.inc

@@ -575,8 +575,11 @@ class OBOImporter extends TripalImporter {
    *
    */
   public function postRun() {
+    
+    // Clear the cached terms
+    cache_clear_all('tripal_chado:term:*', 'cache', TRUE);
 
-    // Update the cv_root_mview materiailzed view.
+    // Update the cv_root_mview materialized view.
     $this->logMessage("Updating the cv_root_mview materialized view...");
     $mview_id = tripal_get_mview_id('cv_root_mview');
     tripal_populate_mview($mview_id);
@@ -585,7 +588,7 @@ class OBOImporter extends TripalImporter {
     $mview_id = tripal_get_mview_id('db2cv_mview');
     tripal_populate_mview($mview_id);
 
-    // Upate the cvtermpath table for each newly added CV.
+    // Update the cvtermpath table for each newly added CV.
     $this->logMessage("Updating cvtermpath table.  This may take a while...");
     foreach ($this->obo_namespaces as $namespace => $cv_id) {
       $this->logMessage("- Loading paths for vocabulary: @vocab", array('@vocab' => $namespace));

+ 4 - 0
tripal_chado/includes/tripal_chado.cv.inc

@@ -154,6 +154,10 @@ function tripal_cv_cv_edit_form_submit($form, &$form_state) {
   if (strcmp($op, 'Update')==0) {
     $match = array('cv_id' => $cv_id);
     $success = chado_update_record('cv', $match, $values);
+    
+    // Clear the cached terms
+    cache_clear_all('tripal_chado:term:*', 'cache', TRUE);
+    
     if ($success) {
       drupal_set_message(t("Controlled vocabulary updated"));
       drupal_goto('admin/tripal/loaders/chado_vocabs/chado_cvs');

+ 12 - 2
tripal_chado/includes/tripal_chado.vocab_storage.inc

@@ -257,7 +257,14 @@ function tripal_chado_vocab_get_term_children($vocabulary, $accession) {
  * This hook is created by the Tripal module and is not a Drupal hook.
  */
 function tripal_chado_vocab_get_term($vocabulary, $accession) {
-
+  // Cache ID for this term:
+  $cid = 'tripal_chado:term:' . $vocabulary . ':' . $accession;
+  
+  // Check the cache first. Get the term from cache if it's available
+  $cache = cache_get($cid, 'cache');
+  if (isset($cache->data)) {
+    return $cache->data;
+  }
   // It's possible that Chado is not available (i.e. it gets renamed
   // for copying) but Tripal has already been prepared and the
   // entities exist.  If this is the case we don't want to run the
@@ -285,7 +292,10 @@ function tripal_chado_vocab_get_term($vocabulary, $accession) {
   $cvterm = chado_expand_var($cvterm, 'table', 'cvterm_relationship', $options);
   $cvterm = chado_expand_var($cvterm, 'table', 'cvtermprop', $options);
 
-  return _tripal_chado_format_term_description($cvterm);
+  // Cache the term to reduce the amount of queries sent to the database
+  $term =  _tripal_chado_format_term_description($cvterm);
+  cache_set($cid, $term, 'cache', CACHE_TEMPORARY);
+  return $term;
 }
 
 /**

+ 10 - 18
tripal_chado/tripal_chado.module

@@ -450,14 +450,6 @@ function tripal_chado_menu() {
     'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
     'file path' => drupal_get_path('module', 'tripal_chado'),
   );
-  $items['admin/tripal/loaders/pub/changedb'] = array(
-    'page callback' => 'tripal_pub_importer_setup_page_update_remotedb',
-    'page arguments' => array(),
-    'access arguments' => array('use chado_pub_bulk importer'),
-    'type ' => MENU_CALLBACK,
-    'file' => 'includes/loaders/tripal_chado.pub_importers.inc',
-    'file path' => drupal_get_path('module', 'tripal_chado'),
-  );
 
   $items['admin/tripal/loaders/pub/criteria/%/%'] = array(
     'page callback' => 'tripal_pub_importer_setup_page_update_criteria',
@@ -1185,9 +1177,9 @@ function tripal_feature_match_features_page($id) {
     $table_attrs = array('class' => 'tripal-data-table');
     $output = "<p>The following features match the name '$id'.</p>";
     $output .= theme_table(array(
-      'header' => $header, 
-      'rows' => $rows, 
-      'attributes' => $table_attrs, 
+      'header' => $header,
+      'rows' => $rows,
+      'attributes' => $table_attrs,
       'caption' => $caption
     ));
     return $output;
@@ -1204,7 +1196,7 @@ function tripal_chado_mail($key, &$message, $params) {
   $language = $message['language'];
   switch($key) {
     case 'import_report':
-      
+
       $content = [];
       $content[] = [
         '#type' => 'markup',
@@ -1220,10 +1212,10 @@ function tripal_chado_mail($key, &$message, $params) {
           }
           $content[] = [
             '#type' => 'item',
-            '#title' => $title, 
+            '#title' => $title,
             '#markup' => theme_item_list([
               'title' => '',
-              'type' => 'ol', 
+              'type' => 'ol',
               'items' => $pubs,
               'attributes' => [],
             ]),
@@ -1231,10 +1223,10 @@ function tripal_chado_mail($key, &$message, $params) {
         }
       }
       $content = '<html>' . drupal_render($content) . '</html';
-      
+
       $message['subject'] = t('Publication import from !site', array('!site' => $site_name));
-      
-      
+
+
       if (module_exists('htmlmail') or module_exists('mimemail')) {
         $headers = array(
           'MIME-Version' => '1.0',
@@ -1250,7 +1242,7 @@ function tripal_chado_mail($key, &$message, $params) {
       else {
         $message['body'][] =  drupal_html_to_text($content);
       }
-            
+
       break;
   }
 }

+ 4 - 0
tripal_ds/theme/css/tripal_ds.css

@@ -74,4 +74,8 @@ span.download-icon {
   cursor: pointer;
   margin-top: 4px;
   color: #777777;
+}
+
+.ds-hide {
+  display: none;
 }

+ 1 - 0
tripal_ds/theme/js/tripal_ds.js

@@ -34,6 +34,7 @@
           }
         });
       });
+      
       // Move the tripal pane to the first position when its TOC item is clicked.
       $('.tripal_pane-toc-list-item-link').each(function (i) {
         var id = '.tripal_pane-fieldset-' + $(this).attr('id');

+ 35 - 21
tripal_ds/tripal_ds.module

@@ -467,20 +467,25 @@ function tripal_ds_field_display_alter(&$display, $context){
     $field_name = $context['field']['field_name'];
     $bundle = $context['entity']->bundle;
     $bundle_info = tripal_load_bundle_entity(array('name' => $bundle));
-    $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id, 'hide');
+    $hide_variable = tripal_get_bundle_variable('hide_empty_field', $bundle_info->id);
+    $processed = isset($context['entity']->{$field_name}) ? $context['entity']->{$field_name}['#processed'] : false;
 
-    if ($field_name && ($hide_variable == 'hide')) {
+    if ($field_name && $hide_variable == TRUE && $processed) {
       $item = field_get_items('TripalEntity', $context['entity'], $field_name);
       $field = field_info_field($field_name);
       if ($item) {
-        if (tripal_field_is_empty($item[0], $field)) {
+        if (tripal_field_is_empty($field, $item)) {
           $parent_field_info = tripal_ds_find_field_group_parent($field_name, 'TripalEntity', $bundle, $context);
           if (!empty($parent_field_info)) {
-            foreach ($parent_field_info as $parent_key => $parent_field){
-               // We want to use JavaScript to remove the fields rather than
-               // CSS to hide them so that when users theme the table of
-               // contents using CSS they aren't theming empty rows.
-               drupal_add_js('jQuery(document).ready(function () { jQuery("#' . $parent_field_info[$parent_key] . '").parents(".views-row").remove() });', 'inline');
+            foreach ($parent_field_info as $parent_key => $parent_field) {
+              if (strpos($parent_field, 'single_field') !== false) {
+                $field_name = str_replace("_single_field", "", $parent_field);
+                drupal_add_js('jQuery(document).ready(function () { jQuery("#tripal-entity-' . $context['entity']->id . '--' . $field_name . '").addClass("ds-hide"); });', 'inline');
+              }
+              // We want to use JavaScript to remove the fields rather than
+              // CSS to hide them so that when users theme the table of
+              // contents using CSS they aren't theming empty rows.
+              drupal_add_js('jQuery(document).ready(function () { jQuery("#' . $parent_field_info[$parent_key] . '").parents(".views-row").remove() });', 'inline');
             }
           }
         }
@@ -505,6 +510,7 @@ function tripal_ds_find_field_group_parent($field_name, $entity_type, $bundle, $
   $field_groups_to_hide = array();
   $increment = 0;
 
+
   // Get the field groups associated with this bundle.
   $fg_for_bundle = db_select('field_group', 'fg')
     ->fields('fg')
@@ -520,24 +526,30 @@ function tripal_ds_find_field_group_parent($field_name, $entity_type, $bundle, $
       // Do nothing
     }
     elseif (!empty($field_group_data['children'][0])) {
+      $children_added = 0;
       $children = $field_group_data['children'];
       //If there is more than one child all need to be checked.
       if (count($children) > 1) {
-        foreach ($children as $kids => $child) {
-          // Now check if each child if empty.
+        foreach ($children as $order => $child) {
+          // Now check if each child is empty.
           $item = field_get_items('TripalEntity', $context['entity'], $child);
           $field = field_info_field($child);
-          if(!tripal_field_is_empty($item[0], $field)){
-            //If any of the fields are not empty do not add the parent.
-            break 2;
-          }
-          else {
+          if(tripal_field_is_empty($field, $item)){
+            //If any of the fields are not empty do not add the parent, add the specific field.
+            // break 2;
+            if (!empty($field)) {
+              $field_groups_to_hide[$increment] = $field['field_name'] . '_single_field';
+              $increment++;
+              $children_added++;
+            }
+          } else {
             continue;
           }
         }
-        $field_groups_to_hide[$increment] = $field_group->group_name;
-      }
-      elseif($children[0] == $field_name) {
+        if ($children_added === count($children)) {
+          $field_groups_to_hide[$increment] = $field_group->group_name;
+        }
+      } elseif ($children[0] == $field_name) {
         $field_groups_to_hide[$increment] = $field_group->group_name;
       }
     }
@@ -557,7 +569,8 @@ function tripal_ds_find_field_group_parent($field_name, $entity_type, $bundle, $
  * @param $bundle
  * @param array $fields
  */
-function tripal_ds_toc_order($bundle, $fields = array()){
+function tripal_ds_toc_order($bundle, $fields = array())
+{
   // Find all menu items associated with the bundle id.
   $menu_items = db_select('tripal_ds', 'ds')
     ->fields('ds')
@@ -571,7 +584,7 @@ function tripal_ds_toc_order($bundle, $fields = array()){
     if (array_key_exists($toc_field_name, $fields)) {
       $weight = $fields[$toc_field_name]['weight'];
       //If a weight is returned update the tripal_ds table.
-      if(!empty($weight)){
+      if (!empty($weight)) {
         db_update('tripal_ds')
           ->fields(array(
             'weight' => $weight,
@@ -589,6 +602,7 @@ function tripal_ds_toc_order($bundle, $fields = array()){
  *
  *
  */
-function tripal_ds_import_api() {
+function tripal_ds_import_api()
+{
   module_load_include('inc', 'tripal_ds', 'api/tripal_ds.pane.api');
 }

+ 6 - 6
tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc

@@ -248,7 +248,7 @@ class TripalContentService_v0_1 extends TripalWebService {
 
     // If the entity is set to hide fields that have no values then we
     // want to honor that in the web services too.
-    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
+    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id);
 
     // Get information about the fields attached to this bundle and sort them
     // in the order they were set for the display.
@@ -320,7 +320,7 @@ class TripalContentService_v0_1 extends TripalWebService {
           $this->addResourceProperty($resource, $term, $service_path . '/' . $entity->id . '/' . urlencode($term['name']), array('lowercase', 'spacing'));
         }
         else {
-          if ($hide_fields == 'show') {
+          if ($hide_fields == FALSE) {
             $this->addResourceProperty($resource, $term, NULL, array('lowercase', 'spacing'));
           }
         }
@@ -340,7 +340,7 @@ class TripalContentService_v0_1 extends TripalWebService {
 
     // If the entity is set to hide fields that have no values then we
     // want to honor that in the web services too.
-    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
+    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id);
 
     // Get the field  settings.
     $field_name = $field['field_name'];
@@ -371,7 +371,7 @@ class TripalContentService_v0_1 extends TripalWebService {
       }
     }
 
-    if ($hide_fields == 'hide' and empty($values[0])) {
+    if ($hide_fields == TRUE and empty($values[0])) {
       return;
     }
 
@@ -436,7 +436,7 @@ class TripalContentService_v0_1 extends TripalWebService {
 
     // If the entity is set to hide fields that have no values then we
     // want to honor that in the web services too.
-    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id, 'hide');
+    $hide_fields = tripal_get_bundle_variable('hide_empty_field', $bundle->id);
 
     $new_value = '';
     // If the value is an array rather than a scalar then map the sub elements
@@ -446,7 +446,7 @@ class TripalContentService_v0_1 extends TripalWebService {
       foreach ($value as $k => $v) {
 
         // exclude fields that have no values so we can hide them
-        if (!isset($v) and $hide_fields == 'hide') {
+        if (!isset($v) and $hide_fields == TRUE) {
           continue;
         }
 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä