tethysplatform / tethys

The Tethys Platform main Django website project repository.
http://tethysplatform.org/
BSD 2-Clause "Simplified" License
91 stars 50 forks source link

Questions about the MapLayout table 'Action' button #1008

Closed PaulDudaRESPEC closed 3 months ago

PaulDudaRESPEC commented 5 months ago

I'm exploring Tethys as a toolkit to build an app for a client. I'm looking specifically at how I can customize the PlotTable feature within the MapLayout. I see that the PlotTable feature gives me the 'Plot' button and an optional 'Action' button. I don't see a way to do 2 things I would need -- first, a way to customize the text of the 'Action' button; and second, a simple way to override the click event on the 'Action' button with my own code.

Do these features exist? Since I'm very new to Tethys I know I may be misunderstanding something; on the other hand this might be a feature request. Either way I would appreciate if someone could patiently point me in the right direction. Thanks!

romer8 commented 5 months ago

Hi @PaulDudaRESPEC . You are right the PlotTable feature gives me the 'Plot' button and an optional 'Action' button. There is a function that can be overwritten in the MapLayout JavaScript API, you can use the following snipped:

  MAP_LAYOUT.action_button_generator(function(feature) {
      let layer_name = MAP_LAYOUT.get_layer_name_from_feature(feature);
      let fid = MAP_LAYOUT.get_feature_id_from_feature(feature);

      // Check if layer is plottable
      let layer = m_layers[layer_name];
      if (!layer || !layer.tethys_data.has_action) {
          return;
      }

      // Build Action Button Markup
      let action_button = "";
      if (layer_name.endsWith("magic_button")) {
          action_button =
              '<div class="action-btn-wrapper">' +
                  '<a 'role="button"'
                  '>{YOUR CUSTOM TEXT}</a>' +
              '</div>';
      }
      return action_button;
  });

This should give you the ability to add a custom text in the button, and also it should give you the ability to add any extra functionality to the action button.

You should enable the has_action property to True in the build_wms_layer or build_geojson_layer methods in the MapLayout class in the controllers depending the type of layer you are using

Lamentably, it is not in the documentation as you can see : https://docs.tethysplatform.org/en/latest/tethys_sdk/layouts/map_layout.html

But it can be seen here in the tethys source code at: https://github.com/tethysplatform/tethys/blob/9f5b60c787c1c3f2bca29df83d3da64b1377f532/tethys_layouts/static/tethys_layouts/map_layout/map_layout.js#L1899

PaulDudaRESPEC commented 5 months ago

Thank you! I'll give it a try!

PaulDudaRESPEC commented 5 months ago

Where do I put that code snippet within my tethys app? What file and folder? Thanks in advance!

romer8 commented 5 months ago

You should put the snippet inside the JavaScript file that you are importing. This is generally the main.js file that gets created when the tethys application gets scaffolded, but if you are using a custom template it would be the imported JavaScript file in the block scripts portion for example:

template custom_map_layout.html
{% extends "tethys_layouts/map_layout/map_layout.html" %}
{% load static %}

{% block scripts %}
  {{ block.super }}
  <script src="{% static 'layout_showcase/js/map.js' %}" type="text/javascript"></script>
{% endblock %}
Tell the Layout to use the custom template using the template_name property:
class MyMapLayout(MapLayout):
    template_name = 'my_first_app/custom_map_layout.html'
    ...

More can be found at : https://docs.tethysplatform.org/en/stable/tethys_sdk/layouts.html#layout-custom-template in the section Custom Template and JavaScript

PaulDudaRESPEC commented 5 months ago

Thank you for your response. As far as I can tell I've done exactly what those instructions indicate. I must be missing something else.

romer8 commented 5 months ago

@PaulDudaRESPEC, can you provide the repo of your code? or can you provide a reproducible example? Also if needed we can arrange a video call meeting and we can go through it together.

PaulDudaRESPEC commented 4 months ago

Since I'm just trying to learn a bit about tethys and evaluate it as a platform we might want to build off, at this point I'm just using your map_layout_tutorial and trying to customize it a bit. The zip file below contains my slightly customized version of that tutorial:

tethysapp-map_layout_tutorial-extended.zip

Thank you for your continued support!

romer8 commented 4 months ago

@PaulDudaRESPEC, I was able to make the button work. I am attaching a zipped file with the updated source code: tst.zip I created a new js file called custom.js:

MAP_LAYOUT.action_button_generator(function(feature) {
    let layer_name = MAP_LAYOUT.get_layer_name_from_feature(feature);
    console.log(layer_name);

    // Correctly attaching an inline onClick handler that logs when the button is clicked.
    // Note: Directly using layer_name within the handler here for demonstration; adjust as needed.
    let action_button = `
        <div class="action-btn-wrapper">
            <a href="javascript:void(0);" role="button" class="btn btn-primary" onClick="(function() { console.log('Button for ${layer_name.replace(/'/g, "\\'")} clicked'); })()">
                ${layer_name ? layer_name : 'Action'}
            </a>
        </div>
    `;

    return action_button;
});

and edit the custom_map_layout.html:

{% extends "tethys_layouts/map_layout/map_layout.html" %}

{% load static %}

{% load tethys_gizmos %}

{% block scripts %}
  {{ block.super }}
  <script src="{% static 'map_layout_tutorial/js/custom.js' %}" type="text/javascript"></script>
{% endblock %}

This would only work for the catchment layer, and if it wants to be reproduced to other layers, the has_action needs to be set to true in the controller for the specific layer

PaulDudaRESPEC commented 4 months ago

Thank you, it worked using the code in your zip file! I think I had something wrong for the action button in my js file from the beginning that was making it not work all along.

If I could trouble you with one more detail, could you extend your example to do something on the 'onClick' event for the custom action button? I'd like to do some custom python when the user clicks that button.

romer8 commented 4 months ago

Hi @PaulDudaRESPEC, sorry for the late reply I hope this is still helpful to you. I think in order to have some custom python code to work with your button. You might need something like this below:

MAP_LAYOUT.action_button_generator(function(feature) {
    let layer_name = MAP_LAYOUT.get_layer_name_from_feature(feature);

    // Assuming fetch API is available for making HTTP requests
    // Note: Adjust the URL as needed for your actual endpoint.
    let callEndpoint = async (layerName) => {
        try {
            const response = await fetch('test-controller/', {
                method: 'POST', // or 'GET' depending on your endpoint
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ layerName: layerName }), // Send layer_name as part of the request body, adjust as needed.
            });
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            const data = await response.json();
            console.log(data); // Handle the response data as needed
        } catch (error) {
            console.error('Error:', error);
        }
    };

    let action_button = `
        <div class="action-btn-wrapper">
            <a href="javascript:void(0);" role="button" class="btn btn-primary" onClick="(function() { (${callEndpoint.toString()})('${layer_name.replace(/'/g, "\\'")}'); })()">
                ${layer_name ? layer_name : 'Action'}
            </a>
        </div>
    `;

    return action_button;
});

and where you have your map layout controller you can add the following below:

@controller
def test_controller(request):
    print("I am here")
    pass

Notice that we are calling the controller url using the fetch library. There is also another way to add custom functions inside your maplayout controller. The following might be helpful: https://docs.tethysplatform.org/en/latest/tethys_sdk/layouts.html#easy-rest-endpoints

PaulDudaRESPEC commented 4 months ago

Awesome, I'll give that a try. Thank you again!