1

it's my first time using a server and the fetch function and I don't understand much about it. Can someone help? Bascially I have fetched a spatial layer that I want to add to my map, but this has to be done through the push of a button. When I worked without the server it worked perfectly, and now it doesn't. Therefore my question is : how do I get a variable out of the fetching technique ?

Here is my code :

fetch('loisirs_all_repro', { credentials: 'include' })
    .then(response => response.json())
    .then(data => {
        console.log(data);
        process1(data);
    })
;
function process1(donnees){
  var bati_loisirs = L.geoJson(donnees,{
    style:styleall,
  });
  function styleall(features) {
    return {
        weight:12,
        opacity: 0.1,
        color: getColor(features.properties.Intensite),
        dashArray: '1',
        fillOpacity: 1
    };
  };
}

/*Display/hide the spatial layer when button is clicked*/

$("#button3").click(function(event) {
    event.preventDefault();
    if(map.hasLayer(bati_loisirs)) {
        $(this).removeClass('selected');
        map.removeLayer(bati_loisirs);
    } else {
        map.addLayer(bati_loisirs);        
        $(this).addClass('selected');
   }
}); 
cam_gis
  • 37
  • 6

1 Answers1

0

This extensive answer on getting data out of an asynchronous call might be helpful, but the immediate problem is that your bati_loisirs variable only exists within the scope of your process1 function. Once that function exits bati_loisirs no longer exists.

So when you do map.hasLayer(bati_loisirs) in your button click handler you're going to get an error. Here's a minimal demonstration of the problem. This doesn't work:

function a() {
  let bati_loisirs = 'foo'; // only exists within function a
}

function b() {
  console.log(bati_loisirs); // Uncaught ReferenceError: bati_loisirs is not defined
}

a();
b();

One remedy would be to move the bati_loisirs variable declaration out to the global scope so it's available everywhere:

let bati_loisirs;

function a() {
  bati_loisirs = 'foo'; // reassigning variable from outer scope
}

console.log(bati_loisirs); // 'undefined', hasn't been set yet.
a();
console.log(bati_loisirs); // 'foo', value changed by a() call

The primary problems with this approach are that it pollutes the global scope with your variables and has potential for variable name collisions. (If some other script also declares a bati_loisirs variable, something's going to break.)

A better solution would be to move everything into the same scope, which in your specific case is pretty straightforward:

fetch('loisirs_all_repro', {
    credentials: 'include'
  })
  .then(response => response.json())
  .then(data => {
    console.log(data);
    // get the layer
    const bati_loisirs = process1(data);

    // add the click handler
    $("#button3").click(function(event) {
      event.preventDefault();
      if (map.hasLayer(bati_loisirs)) {
        $(this).removeClass('selected');
        map.removeLayer(bati_loisirs);
      } else {
        map.addLayer(bati_loisirs);
        $(this).addClass('selected');
      }
    });
  });

function process1(donnees) {
  // return the layer so the caller can use it
  return L.geoJson(donnees, {
    style: features => ({
      weight: 12,
      opacity: 0.1,
      color: getColor(features.properties.Intensite),
      dashArray: '1',
      fillOpacity: 1
    }),
  });
}

This assumes you don't need the reference to bati_loisirs anywhere else.

Finally, if you didn't want to do it all within the fetch, you could await the response:

async function setUp() {
  const data = await fetch('loisirs_all_repro', {
    credentials: 'include'
  })
  .then(response => response.json())

  const bati_loisirs = process1(data);

  // add the click handler
  $("#button3").click(function(event) {
    event.preventDefault();
    if (map.hasLayer(bati_loisirs)) {
      $(this).removeClass('selected');
      map.removeLayer(bati_loisirs);
    } else {
      map.addLayer(bati_loisirs);
      $(this).addClass('selected');
    }
  });
}

function process1(donnees) {
  // return the layer so the caller can use it
  return L.geoJson(donnees, {
    style: features => ({
      weight: 12,
      opacity: 0.1,
      color: getColor(features.properties.Intensite),
      dashArray: '1',
      fillOpacity: 1
    }),
  });
}

setUp();

Edit: Using Modules

Anything beyond the simplest toy apps can quickly become difficult to manage and maintain, which is why javascript has added support for modules, allowing you to break up your app into logical chunks, where each chunk (module) can focus on a specific thing.

I recommend you read the excellent MDN guide on modules, but here's a quick sketch of how you might apply it to your current task.

The basic idea is that you would put all of your layer management into one module, and only expose the parts that are useful outside the module itself.

So you might have a getLayer function that your app could use to get a particular layer. All the fetching and parsing logic is abstracted away, hidden in the getLayer implementation:

import { getLayer } from 'layers.js';

const bati_loisirs = getLayer('loisirs_all_repro');

// do stuff with bati_loisirs

You can set this up by creating two modules: 1) a "layers" module that exports "getLayers", and 2) an "app" module that uses the layers module. (Note that the names "layers" and "app" are arbitrary. You can call them whatever you want.)

In practice your layers.js module might look something like this:

// layers.js

// A mapping of paths to layers. Not exported, so only visible within this module
const layers = {};

 // exported. this is the only part of the module visible from the outside.
export async function getLayer(path) {
    if (!layers[path]) {
        // if we don't already have it do the fetch and add it to the mapping
        layers[path] = await fetchLayer(path);
    }
    // return the layer for the given path from the mapping
    return layers[path];
}

// "private" functions for fetching and processing data

async function fetchLayer(path) {
    return fetch('loisirs_all_repro', { credentials: 'include' })
        .then(response => response.json())
        .then(data => process1(data))
}

function process1(donnees){
  return L.geoJson(donnees,{
    style: () => ({
        weight:12,
        opacity: 0.1,
        color: getColor(features.properties.Intensite),
        dashArray: '1',
        fillOpacity: 1
    }),
  });
}

Then your app.js module can import getLayers and use it, as in the example above:

// app.js

import { getLayer } from './layers';

async function app () {
  const bati_loisirs = getLayer('loisirs_all_repro');
  // do stuff with bati_loisirs
}

app();

You can use this in the browser by including the main module in your html page and specifying type="module":

<script type="module" src="./app.js"></script>

I know this is a lot to take in. Hope this helps.

ray
  • 26,557
  • 5
  • 28
  • 27
  • Just another quick question : if I were to use "bati_loisirs" somewhere else, say in a context in which I need other fetched layers to be used as well, what would be the solution? Thanks! – cam_gis Mar 04 '22 at 11:50
  • 1
    It's impossible to say what the optimal solution is without knowing more about your setup and what you're trying to do, but [javascript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) are very useful for keeping complex apps manageable while avoiding the name collisions and global scope pollution problems mentioned above. I'll update my answer with some pseudocode using modules that might be helpful. – ray Mar 04 '22 at 16:05
  • 1
    Answer updated with a (probably very unclear and confusing) section on modules. – ray Mar 04 '22 at 16:42
  • Wow - I understood & it's exactly what I needed ! Thank you so so much for taking the time to explain further! – cam_gis Mar 05 '22 at 15:20