Access dropdown in view (Javascript)

I need help with figuring out what’s wrong with this code.

I have a form where we are selecting registrations for a course. The registrations in this form is stored in its own table (Registrations). The form only contains one field (a dropdown) which will show relevant employees. All employees are connected to a company, and a project (separate table) can connect to one or more companies.

A course is connected to a project. And when adding a registrations we would like to show only employees for the companies in the current project.

The below code manages to get the companies (organtizations), one or more, and also get the right employees. This debug log contains an array with the employees we would like to have in the dropdown: console.log(“Suksessfullt hentet ansatte:”, response); (success getting employees).

It seems like the code that will put the content of the array in the dropdown do not work. When I tried to set this code “var employeeDropdown = $(‘#view_235_field_72_chzn’);”, just to see if this shows the correct employees, it did, but not in a dropdown.

So the question will be: how to access a dropdown and make changes to it!

$(document).on('knack-view-render.view_235', function(event, view, data) {
  console.log("Visning renderes: view_235");
  
  const projectId = getProjectIdFromURL();
  console.log("Hentet prosjekt-ID:", projectId);
  
  if (!projectId) {
    return console.error('Kunne ikke finne prosjekt-ID.');
  }

  getOrganizationsForProject(projectId, function(orgIDs) {
    console.log("Organisasjons-IDs hentet for prosjekt:", orgIDs);
    getEmployeesForOrganizations(orgIDs);
  });
});

function getProjectIdFromURL() {
  var hash = window.location.hash;
  var match = hash.match(/view-kp-project-details\/([a-f0-9]+)/);
  console.log("URL hash:", hash);
  return match ? match[1] : null;
}

function getOrganizationsForProject(projectId, callback) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_10/records';
  var filters = JSON.stringify({
    "match": "and",
    "rules": [
      {
        "field": "field_41",
        "operator": "is",
        "value": projectId
      }
    ]
  });

  console.log("Kaller API for å hente organisasjoner med filter:", filters);

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'X-Knack-Application-Id': 'xxxxxxxxxxxxxxxxx',
      'X-Knack-REST-API-Key': 'xxxxxxxxxxxxxxxx'
    },
    data: {
      'filters': filters
    },
    success: function(response) {
      console.log("Suksessfullt hentet organisasjoner:", response.records);
      var orgIDs = response.records.map(function(record) {
        var wrapper = document.createElement('div');
        wrapper.innerHTML = record.field_42;
        var span = wrapper.firstChild;
        return span.className; // Her antar vi at klassenavnet er ID-en
      });
      callback(orgIDs);
    },
    error: function(xhr) {
      console.error("Feil ved henting av virksomheter for prosjekt:", xhr);
    }
  });
}

function getEmployeesForOrganizations(orgIDs) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_12/records';
  var filters = JSON.stringify({
    "match": "or",
    "rules": orgIDs.map(function(orgID) {
      return {
        "field": "field_48",
        "operator": "is",
        "value": orgID
      };
    })
  });

  console.log("Kaller API for å hente ansatte med filter:", filters);

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'X-Knack-Application-Id': 'xxxxxxxxxxxxxxx',
      'X-Knack-REST-API-Key': 'xxxxxxxxxxxxxxx'
    },
    data: {
      'filters': filters
    },
    success: function(response) {
      console.log("Suksessfullt hentet ansatte:", response);
      var employeeDropdown = $('#view_235-field_72');
      
      console.log("Dropdown element:", employeeDropdown);
      employeeDropdown.empty();
      
      if(response.records.length > 0){
        $.each(response.records, function(index, employee) {
          console.log(`Legger til ansatt: ${employee.field_246} med ID: ${employee.id}`);
          employeeDropdown.append($('<option>').val(employee.id).text(employee.field_246)); 
        });
      } else {
        console.log("Ingen ansatte funnet, legger til standardvalg.");
        employeeDropdown.append($('<option>').text('Ingen ansatte funnet'));
      }
    },
    error: function(xhr) {
      console.error("Feil ved henting av ansatte:", xhr);
    }
  });
}

Hi @Ronny

Can you look up

$(select).trigger(‘liszt: updated’)

This is because in Knack, all selects use jQuery Chosen() plugin. So once you have edited a select either single or multi you need to trigger an update on the select. If you dive in to the html you should see the select and a ul element. If you make changes you need to update the select element.

Let me know if you need more info but I can spend time sending a more in-depth response tomorrow.

Craig

Thank you very much for responding!!

This is my latest version where I was trying out something like what you mention. I replaced your code (last line). No success.

$(document).on('knack-view-render.view_235', function(event, view, data) {
  var projectId = getProjectIdFromURL();
  
  if (projectId) {
    getOrganizationsForProject(projectId, function(orgIDs) {
      getEmployeesForOrganizations(orgIDs);
    });
  } else {
    console.error('Kunne ikke finne prosjekt-ID.');
  }
});

function getProjectIdFromURL() {
  var hash = window.location.hash;
  var match = hash.match(/view-kp-project-details\/([a-f0-9]+)/);
  return match ? match[1] : null;
}

function getOrganizationsForProject(projectId, callback) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_10/records';
  var filters = JSON.stringify({
    "match": "and",
    "rules": [
      {
        "field": "field_41",
        "operator": "is",
        "value": projectId
      }
    ]
  });

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'X-Knack-Application-Id': 'xxxxxxxxxxxxxxxxxxxxxxx',
      'X-Knack-REST-API-Key': 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
    },
    data: { 'filters': filters },
    success: function(response) {
      var orgIDs = response.records.map(function(record) {
        return record.id; // Assuming 'id' is the correct identifier for organizations
      });
      callback(orgIDs);
    },
    error: function(xhr) {
      console.error("Error fetching organizations for project:", xhr);
    }
  });
}

function getEmployeesForOrganizations(orgIDs) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_12/records';
  var filters = JSON.stringify({
    "match": "or",
    "rules": orgIDs.map(function(orgID) {
      return { "field": "field_48", "operator": "is", "value": orgID };
    })
  });

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'X-Knack-Application-Id': 'xxxxxxxxxxxxxxxxxxxxx',
      'X-Knack-REST-API-Key': 'xxxxxxxxxxxxxxxxxxxxxxxx'
    },
    data: { 'filters': filters },
    success: function(response) {
      updateChosenDropdown(response.records);
    },
    error: function(xhr) {
      console.error("Error fetching employees:", xhr);
    }
  });
}

function updateChosenDropdown(employees) {
  var $select = $('#view_235-field_72');
  $select.empty();

  if (employees.length > 0) {
    $select.append($('<option>').val('').text('Velg')); // Adding a default option
    $.each(employees, function(index, employee) {
      $select.append($('<option>').val(employee.id).text(employee.field_246)); // Replace 'field_246' with the actual field for employee name
    });
  } else {
    $select.append($('<option>').text('Ingen ansatte funnet'));
  }

  //$select.trigger("chosen:updated");
  $(select).trigger("liszt: updated");
}

This is the HTML with the dropdown:
knack1

Hey Ronny,

I see that this code is being used client side in Knack’s JavaScript. Just so you’re aware, exposing your API-KEY in this section is NOT SAFE.

This code is public, and placing the API-KEY here allows attackers to be able to query, modify, and even delete all of your data.

Knack itself has in their documentation when and when not to use object based requests:

I cannot stress enough the importance of using view based requests in your Knack app. They are far more secure and were built for this very purpose. Here’s the attached documentation:

Best,

Thank you for the heads up!

I’m not a professional developer, and was thinking a no-code tool would be a nice way to transfer our Access/mySQL apps into 2024. It seems like we pretty fast will need coding, even for simple functionality.

Maybe we should buy one hour from a Knack Expert to get some advice.

Hi Ronny

Just to let you know that I will get back to you with what I think you need to do some time today. Sorry I have been very busy. I will need to test the code before I post it.

Craig

Very happy to hear that!

Trying out some things now, but really kind of clueless :slight_smile:

$(document).on('knack-view-render.view_235', function(event, view, data) {
  var projectId = getProjectIdFromURL();
  
  if (projectId) {
    getOrganizationsForProject(projectId, function(orgIDs) {
      getEmployeesForOrganizations(orgIDs);
    });
  } else {
    console.error('Kunne ikke finne prosjekt-ID.');
  }
});

function getProjectIdFromURL() {
  var hash = window.location.hash;
  var match = hash.match(/view-kp-project-details\/([a-f0-9]+)/);
  return match ? match[1] : null;
}

function getOrganizationsForProject(projectId, callback) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_10/records';
  var filters = JSON.stringify({
    "match": "and",
    "rules": [
      {
        "field": "field_41",
        "operator": "is",
        "value": projectId
      }
    ]
  });

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'Authorization': Knack.getUserToken() // Bruker autentisering med brukerens token
    },
    data: { 'filters': filters },
    success: function(response) {
      var orgIDs = response.records.map(function(record) {
        console.log(record.id);
        return record.id;
      });
      callback(orgIDs);
    },
    error: function(xhr) {
      console.error("Error fetching organizations for project:", xhr);
    }
  });
}

function getEmployeesForOrganizations(orgIDs) {
  var apiUrl = 'https://api.knack.com/v1/objects/object_12/records';
  var filters = JSON.stringify({
    "match": "or",
    "rules": orgIDs.map(function(orgID) {
      return { "field": "field_48", "operator": "is", "value": orgID };
    })
  });

  $.ajax({
    url: apiUrl,
    type: 'GET',
    headers: {
      'Authorization': Knack.getUserToken() // Bruker autentisering med brukerens token
    },
    data: { 'filters': filters },
    success: function(response) {
      updateChosenDropdown(response.records);
    },
    error: function(xhr) {
      console.error("Error fetching employees:", xhr);
    }
  });
}

function updateChosenDropdown(employees) {
  var $select = $('#view_235-field_72');
  console.log(employees);
  $select.empty();

  if (employees.length > 0) {
    $select.append($('<option>').val('').text('Velg'));
    $.each(employees, function(index, employee) {
      $select.append($('<option>').val(employee.id).text(employee.field_246));
    });
  } else {
    $select.append($('<option>').text('Ingen ansatte funnet'));
  }

  $(select).trigger("liszt:updated");
}



Hi Ronny

Could you please let me know why you are doing this in code? There are ways to filter connections in the forms. When you click on a connection field in the builder you can choose to filter by other connections. Is this something you have explored?

Craig

We were trying to do that during an Office Hours-session, but we were not able to make it work.

The reason is probably that the tables are not “close enough”…

I’ll try to sum this up:

  • A project consists of one or more organizations, an organization can connect with one or more projects
  • An organization have a set of employees
  • A course (KP_Kurs) connects with a project and can have participants from all of the organizations’s employees in the current project. The participants is stored in the KP_Påmelding (registrations) table and contains a connection to the employee, course and organization

In the course information page, we have “ny påmelding” (new registration).

That shows this form:

We are not able to filter by the one or more organizations’ employees here.

So I would suggest that you add a connection to the organisation into the object that holds the employee field.
Once that field is there you can programmatically select the organisation. Then in your employee field you can filter it by the organisation. You would then hide the organisation field using visibility: hidden. Essentially the organisation field would work as a dummy field purely to filter the employees.

Example:

image

image

I will provide the code for you in a bit but this would simplify your process as you would only need to get the organisation ID then select it in the Organisation field which will then filter the employees.

Craig

Thank you.

I do not grasp this, but I’ll think some code will help! I’m not sure which table we are talking about here: “add a connection to the organisation into the object that holds the employee field.”

The first code was also working, we had an array with the correct employees, but were just not able to show them in the dropdown. And it would need to be rewritten into a safe state, as Kelson’s suggestions.

I’ll guess that your suggestion would mean lesser code, that would be preferable!

Looking forward to the code!!

So you already have the connection in your object

image

If you add the organisation field to your form you can filter the employees by organisation as long as each employee is connected to an organisation.

This? That makes the employee list (ansatt) empty.

Ok I think I know how to fix your original code so ill work on that now. I just wondered if we could simplify the process.

If you want to make it safe you will need to use view-based gets. For that to work you will need to put grids somewhere in your app that the users have access to.

We add a new page just for API calls that has grids that we need to access. You can then untick the option to hide the page from the menus.
image

The code for your select try this code:

function updateChosenDropdown(employees) {
        const $select = $('#view_235-field_72');
        console.log(employees);

        // Clear all existing options
        $select.empty();

        if (employees.length > 0) {
            $select.append(new Option('Velg'));
            // Add new options
            employees.forEach(employee => {
                $select.append(new Option(employee.field_246, employee.id));
            });
        } else {
            $select.append(new Option('Ingen ansatte funnet'));
        }

        // Update the Chosen dropdown
        $select.trigger("liszt:updated");
    }

This was the result:
image

Let me know if this works

Craig

Just to be sure I understand the safe way right:

  • I’ll make a new page Shared Area Login (at level 1)
  • Ill put two grids here, one with all the organizations and one with all the employees (will the max record count of 100 be a problem?)
  • These are named view_250 and view_251

In the highlighted page I put the grids.

It’s seems like the script can’t access these, based on the error messages.

$(document).on('knack-view-render.any', function(event, view, data) {
  console.log('View loaded:', view.key);

  // Sjekk om den nødvendige visningen er lastet og om den inneholder model-objektet
  if(view.key === 'view_250' || view.key === 'view_251') {
    console.log('View loaded: ' + view.key);
    if (view.model) {
      console.log('Model is accessible for view: ' + view.key);
    } else {
      console.error('Modelis not defined for view: ' + view.key);
    }
  }
});


$(document).on('knack-view-render.view_235', function(event, view, data) {
  console.log('Visning 235 is loaded.');

  var projectId = getProjectIdFromURL();

  if (projectId) {
    console.log('Project-ID found: ' + projectId);
      
    // view_250 is a grid in the Shared Area Login page with all the records from the organizations table
    Knack.views["view_250"].model.fetch({
      success: function(organizations) {
        console.log(organizations.length + ' organisasjoner hentet.');
        var orgIDs = organizations.models.map(function(model) {
          return model.id; // eller den tilsvarende felt-IDen i din grid
        });
        console.log('Organisasjons-IDer: ', orgIDs);
        getEmployeesForOrganizations(orgIDs);
      },
      error: function() {
        console.error('Could not get organizations.');
      }
    });
  } else {
    console.error('Could not find Project-ID.');
  }
});

function getProjectIdFromURL() {
  var hash = window.location.hash;
  var match = hash.match(/view-kp-project-details\/([a-f0-9]+)/);
  if (match) {
    console.log('URL-matching suksessfull: ', match[1]);
  } else {
    console.log('URL-matching mislyktes.');
  }
  return match ? match[1] : null;
}

function getEmployeesForOrganizations(orgIDs) {
  
  // view_251 is a grid in the Shared Area Login page with all the records from the employees table
  Knack.views["view_251"].model.fetch({
    success: function(employees) {
      console.log(employees.length + ' employees loaded.');
      updateChosenDropdown(employees.toData());
    },
    error: function() {
      console.error('Feil ved henting av ansatte.');
    }
  });
}

function updateChosenDropdown(employees) {
        const $select = $('#view_235-field_72');
        console.log(employees);

        // Clear all existing options
        $select.empty();

        if (employees.length > 0) {
            $select.append(new Option('Velg'));
            // Add new options
            employees.forEach(employee => {
                $select.append(new Option(employee.field_246, employee.id));
            });
        } else {
            $select.append(new Option('Ingen ansatte funnet'));
        }

        // Update the Chosen dropdown
        $select.trigger("liszt:updated");
    }

And thank you very much for staying with me :slight_smile: Getting through this I think I’ll have a good foundation for getting along by myself!

1 Like

I think you may have misunderstood.

You are right to have set up your grids in a shared area, however you still need to do an API call using this method (view-based API):

$(document).on('knack-view-render.view_235', async function(event, view, data) {
    console.log(view)
    const projectId = getProjectIdFromURL();
    async function getOrganisations(){
        const sceneId = 'scene_123' //Scene where the view is in the shared area
        const viewId = 'view_456' //Your view with the records
        const filters = {
            "match": "and",
            "rules": [
                {
                    "field": "field_41",
                    "operator": "is",
                    "value": projectId
                }
            ]
        };
        $.ajax({
            url: "https://api.knack.com/v1/pages/" + sceneId + "/views/" + viewId + "/records/?filters=" + encodeURIComponent(JSON.stringify(filters)),
            type: 'get',
            crossDomain: true, //Attempting to reduce the frequent but intermittent CORS error message.
            headers: {
                Authorization: Knack.getUserToken(),
                'X-Knack-Application-Id': Knack.application_id,
                'X-Knack-REST-API-Key': 'knack',
                "Content-Type": "application/json",
                "Access-Control-Allow-Origin": "*.knack.com",
            },
            success: function (response) {
                Knack.hideSpinner();
                resolve(response);
            },
            error: function (response) {
                console.log("response:", response);
                Knack.hideSpinner();
                reject(response);
            },
        });
    }
    const organisations = await getOrganisations();
    const orgIds = orginisations.map(org => org.id);
});

This should return all the organisations from the shared area grid that displays the organisations. You are correct that the 100-record limit doesn’t matter. I have made them async functions because you will need the organisations to resolve before the employees.

You will then need to do the same for the employees that belong to the organisations I hope that makes sense.

I have not tested this exact code but I use this format for all my API calls.

Craig

Some hours later, and I finally got it :slight_smile:

Thank you very much for you help, couldn’t done it without!

$(document).on('knack-view-render.view_235', async function(event, view, data) {
  console.log('Visning 235 er lastet.');
  const projectId = getProjectIdFromURL();

  if (!projectId) {
    console.error('Kunne ikke finne prosjekt-ID.');
    return;
  }

  console.log('Prosjekt-ID funnet: ' + projectId);

  try {
    const orgLinkRecords = await getOrganisationLinks(projectId);
    const orgIds = orgLinkRecords; // Denne variabelen skal inneholde de faktiske IDene

    if (orgIds.length > 0) {
      const employeesResponse = await getEmployees(orgIds);
      updateChosenDropdown(employeesResponse.records);
    } else {
      updateChosenDropdown([]);
     }
  } catch (error) {
    console.error('En feil oppsto:', error);
  }
});

async function getOrganisationLinks(projectId) {
  const sceneId = 'scene_150';
  const viewId = 'view_252'; // View for mange-til-mange-tabellen KP_ManyToMany_Project_org
  const filters = {
    "match": "and",
    "rules": [
      {
        "field": "field_41", // Erstatt med faktisk felt-ID som refererer til prosjektet i KP_ManyToMany_Project_org
        "operator": "is",
        "value": projectId
      }
    ]
  };

  return new Promise((resolve, reject) => {
    Knack.showSpinner();
    $.ajax({
      url: `https://api.knack.com/v1/pages/${sceneId}/views/${viewId}/records`,
      type: 'GET',
      headers: {
        Authorization: Knack.getUserToken(),
        'X-Knack-Application-Id': Knack.application_id,
        'Content-Type': "application/json"
      },
      data: { filters: JSON.stringify(filters) },
      success: function (response) {
        Knack.hideSpinner();
        console.log('Full response from organisation links:', response);

        if (!response.records || response.records.length === 0) {
          resolve([]);
          return;
        }

        // Anta at feltet som inneholder organisasjon-IDen er korrekt
        const orgIds = response.records.map(record => {
          console.log(`Field_42_raw for record:`, record.field_42_raw);
          const orgIdArray = record.field_42_raw;
          // Sjekk om arrayen eksisterer og har minst ett element
          if (orgIdArray && Array.isArray(orgIdArray) && orgIdArray.length > 0) {
            // Sjekk om det første elementet i arrayen har en id
            if (orgIdArray[0].id) {
              return orgIdArray[0].id;
            }
          }
          return undefined; // Eller null, eller en annen indikator for at ingen gyldig ID ble funnet
        }).filter(id => id); // Fjern undefined og null verdier

        resolve(orgIds);
      },
      error: function (response) {
        Knack.hideSpinner();
        console.error('Error getting organisation links:', response);
        reject(response);
      },
    });
  });
}

async function getEmployees(orgIds) {
  const sceneId = 'scene_150';
  const viewId = 'view_251'; // View for ansatt-tabellen
  const filters = {
    "match": "and",
    "rules": [
      {
        "field": "field_48", // Felt-ID for organisasjonen i ansatt-tabellen
        "operator": "in",
        "value": orgIds
      }
    ]
  };
  return new Promise((resolve, reject) => {
    Knack.showSpinner();
    $.ajax({
      url: `https://api.knack.com/v1/pages/${sceneId}/views/${viewId}/records`,
      type: 'GET',
      headers: {
        Authorization: Knack.getUserToken(),
        'X-Knack-Application-Id': Knack.application_id,
        'Content-Type': "application/json"
      },
      data: { filters: JSON.stringify(filters) },
      success: function (response) {
        Knack.hideSpinner();
        console.log('Ansatte respons:', response);
        resolve(response);
      },
      error: function (response) {
        Knack.hideSpinner();
        reject(response);
      },
    });
  });
}


function updateChosenDropdown(employees) {
  var $select = $('#view_235-field_72'); // Sørg for at dette er den riktige selektoren for din dropdown
  $select.empty(); // Tømmer dropdown før oppdatering

  if (employees && employees.length > 0) {
    $select.append($('<option>').val('').text('Velg en ansatt')); // Standardvalg

    employees.forEach(employee => {
     
      var employeeName = employee.field_246; 
      var employeeId = employee.id;
      $select.append($('<option>').val(employeeId).text(employeeName)); // Legger til ansatte som valg
    });
  } else {
    $select.append($('<option>').text('Ingen ansatte funnet')); // Hvis ingen ansatte ble funnet
  }

  $select.trigger('liszt:updated'); // Trigger oppdatering for Chosen plugin

}


function getProjectIdFromURL() {
  var hash = window.location.hash;
  var match = hash.match(/view-kp-project-details\/([a-f0-9]+)/);
  return match ? match[1] : null;
}


1 Like

That’s great glad I could help.