10

Dashboard Template

Hello everyone!

After the    post, I decided to start a new one. This will make it easier to follow the changes.

New features have been added:

  • Sticky buttons. Thanks to   CSS hacks, I was able to make the buttons component sticky. Now the buttons always remain visible to the top of the page as we scroll down.
  • Scroll into View. By clicking a button the page automatically scrolls into the correspoding section. In the attached database press the first button "Accounts" to see it in action.

The combination of the above two features has the potential to transform the way we design our Dashboards. Like the one-page websites, we can present our content in sections on one long Dashboard.

In Safari works but I haven't check it in other browsers. Let me know if you find any bug.

What other functionalities would you like to see?

Leave here your suggestions, ideas or improvements!

Enjoy!

220 replies

null
    • szormpas
    • 6 mths ago
    • Reported - view

    Version 8.2

    Video player:

    Hello everyone,

     mentioned in this post that we could use HTML to play our videos in Ninox.

    Although I have no experience in video management, I could not resist his kind encouragement to add similar functionality to the dashboard.

    So, I spent a few hours searching the web for an HTML5 player that would work out of the box, be lightweight, and allow for customization.

    I present Vidstack as a proof of concept rather than a complete Ninox solution.

    It is welcome if anyone has a suggestion for a better player or would like to contribute in any way.

    At least enjoy the Sprite Fright short film! ๐Ÿ˜‚

     

      • gold_cat
      • 6 mths ago
      • Reported - view

       Cool~Your selfless help has benefited me greatly.

    • szormpas
    • 5 mths ago
    • Reported - view

    Version 9.0

    Input Form widget

    Hello everyone,

    After several days of hard work and a few challenges along the way, I'm happy to present my new widget. It's a custom data entry form in Ninox to replace the familiar left-sliding native form.

    So, what's new with this widget?

    • It's a pop-up design that lets you interact with the background (after opening, it remains visible unless you choose to cancel).
    • You can move (drag) it anywhere on the screen.
    • The cursor can be auto-focused on any field.
    • It supports almost all Ninox fields, like text, password, date, option, and so on.
    • The widget makes sure the data you enter is correct and meets the right requirements. If it isn't, you can't submit it, and you'll see an error message (e.g., Field is required).
    • A hide/show svg eye icon to toggle password visibility.
    • You can put it anywhere on your Table forms or Pages since it's just a custom formula button (Create record).

    Paste your Ninox API key into the corresponding field of the 'Settings' table to test it inside the Dashboard Template.

    Enjoy! ๐Ÿ™‚

      • Fred
      • 5 mths ago
      • Reported - view

      That is really cool. You have added a wealth of tools. Thanks, from the community.

      • szormpas
      • 5 mths ago
      • Reported - view

        I forgot to mention that another great feature of the widget is that you can check for duplicates in any field of the table. I've already implemented it for the 'Username' field. Try entering a value in that field that already exists in the table, and you'll see an error message just below the 'Save' button.

      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       Excelent work Sotirios. ๐Ÿ‘

      One question the new calendar ? ๐Ÿ˜ฎ Is posible to implement in a simple field.

      • szormpas
      • 5 mths ago
      • Reported - view

       Appreciate the feedback! Happy to help and be part of such a great community.

      • szormpas
      • 5 mths ago
      • Reported - view

        Hi, The attached calendar is from your Android tablet? Would you like to see it in a Ninox date field too?

      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       

      Yes is in Android Tablet (Galaxy Tab Ultra). And yes I would like to see it in a Ninox date field.

      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       

      Upsss too different on web version.

      • szormpas
      • 5 mths ago
      • Reported - view

        Yes, this is to be expected and happens because the Widget borrows the default appearance of the date field of each Browser.

      As you'll see in the code, I've set the type of the input field to simply 'date'.

      • gold_cat
      • 5 mths ago
      • Reported - view

       Thanks for giving and I encountered this mistake when I submitted it

      • szormpas
      • 5 mths ago
      • Reported - view

        Hi there, have you added your Ninox API Key to the 'Settings' Table?

      • gold_cat
      • 5 mths ago
      • Reported - view

       I forgot this step, thank you

      • Einkauf
      • 7 days ago
      • Reported - view

       

      Hi everyone, Thank you Sotirios for your work. Actually i'm trying to adapt the Input form, but an error appears. any ideas? I have added the API in Settings table. I thing the problem is the ninoxApiUrl but i'm beginner in the field so i'm not sure. any Idea will be very helpful Thank you!

      here is the code:

      let ninoxApiKey := (select Settings)[Name = "Ninox API Key"].Value;
      let teamId := teamId();
      let databaseId := databaseId();
      let tableId := tableId("Tab");
      let ninoxApiUrl := "https://api.ninox.com/v1/teams/" + teamId + "/databases/" + databaseId +
          "/tables/" +
          tableId +
          "/records";
      html("
      <head>
          <style>
      
              #sz-open-btn {
                  border: 1px solid #566eb1;
                  color: #566eb1;
                  background-color: transparent;
                  padding: 2px 20px;
                  font-size: 14px;
                  border-radius: 4px;
                  cursor: pointer;
                  text-align: center;
              }
      
              #sz-open-btn:hover {
                  background-color: #f0f3fb;
              }
      
              .sz-modal {
                  display: none;
                  position: fixed;
                  left: 50%;
                  top: 50%;
                  transform: translate(-50%, -50%);
                  z-index: 10000;
              }
      
              .sz-form {
                  background-color: #fff;
                  padding: 20px;
                  border-radius: 8px;
                  width: 600px;
                  max-height: 600px;
                  overflow-y: auto;
                  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.6);
                  border: 1px solid #ddd;
                  animation: popUp 0.3s ease-out;
              }
      
              @keyframes popUp {
                  from {
                      transform: scale(0.9);
                      opacity: 0;
                  }
                  to {
                      transform: scale(1);
                      opacity: 1;
                  }
              }
      
              .sz-form-header {
                  display: flex;
                    align-items: center;
                    justify-content: center;
                  padding-bottom: 16px;
                  border-bottom: 1px solid #ddd;
                  cursor: move;
                  user-select: none;
              }
      
              .sz-form-header h2 {
                  margin: 0;
                  font-size: 14px;
                  color: #566eb1;
              }
      
              .sz-form-body {
                    display: grid;
                  grid-template-columns: 1fr 1fr;
                    gap: 24px;
                  margin-top: 24px;
              }
      
              .sz-form-body input {
                  font-size: 14px;
                  width: 100%;
                  border: 1px solid #ddd !important;
                  border-radius: 4px;
                  background-color: #f9fafb;
              }
      
              .sz-form-body label {
                  display: flex;
                  align-items: center;
                  font-size: 12px;
                  color: #566eb1;
              }
      
              .sz-form-footer {
                  display: flex;
                  flex-wrap: wrap;
                    justify-content: space-between;
                  border-top: 1px solid #ddd;
                  margin-top: 30px;
                  padding-top: 20px;
                  cursor: move;
                  user-select: none;
              }
      
              .sz-form-footer button {
                  padding: 10px 20px;
                  font-size: 14px;
                  color: white;
                  border: none;
                  border-radius: 4px;
                  cursor: pointer;
              }
      
              #sz-cancel-btn {
                  background-color: #ff4d4d;
              }
      
              #sz-save-btn {
                  background-color: #007bff;
              }
      
              #sz-cancel-btn:hover {
                  background-color: #ff1a1a;
              }
      
              #sz-save-btn:hover {
                  background-color: #0056b3;
              }
      
              .sz-message {
                    display: block;
                    color: red;
              }
      
              #sz-fetch-response {
                  flex-basis: 100%;
                  text-align: right;
              }
      
              @media screen and (max-width: 600px) {
                  .sz-form {
                      width: 300px;
                      max-height: 400px;
                  }
                  .sz-form-body {
                      grid-template-columns: 1fr;
                  }
              }
      
          </style>
      </head>
      <body>
      
          <button id='sz-open-btn' type='button' onclick = 'displayModal()'>Create record</button>
      
          <div id='sz-modal-container' class='sz-modal' >
      
              <div class='sz-form'>
      
                  <div id='sz-form-header-box' class='sz-form-header'>
                      <h2>New Record</h2>
                  </div>
      
                  <div class='sz-form-body'>
      
                      <div>
                      <label for='sz-document'>Name</label>
                      <div>
                      <input id='sz-document' type='text' aria-describedby='sz-document-validation' required>
                      <span id='sz-document-validation' aria-live='assertive' class='sz-message'></span>
                      </div>
                      </div>
      
                      <div>
                      <label for='sz-plan1'>Vorname</label>
                      <input type='text' id='sz-plan1'>
                      </div>
      
                  </div>
      
                  <div id='sz-form-footer-box' class='sz-form-footer' style='flex-basis: 100%;'>
                          <button id='sz-cancel-btn' type='button' onclick = 'resetAndClose()'>Cancel</button>
                          <button id='sz-save-btn' type='button' onclick = 'checkAndSave()'>Save</button>
                          <span id='sz-fetch-response' class='sz-message'></span>
                  </div>
      
              </div>
      
          </div>
      
      <script>
      
          var szModalContainer = document.getElementById('sz-modal-container');
          var szFormHeaderBox = document.getElementById('sz-form-header-box');
          var szFormFooterBox = document.getElementById('sz-form-footer-box');
          var szOpenBtn = document.getElementById('sz-open-btn');
          var szInputs = {
              szDocument: document.getElementById('sz-document'),
              szPlan1: document.getElementById('sz-plan1'),
      
          };
          var szCancelBtn = document.getElementById('sz-cancel-btn');
          var szSaveBtn = document.getElementById('sz-save-btn');
          var szFetchResponse = document.getElementById('sz-fetch-response');
      
          function onMouseDrag({ movementX, movementY }) {
                  var getModalStyle = window.getComputedStyle(szModalContainer);
                  var leftValue = parseInt(getModalStyle.left, 10);
                  var topValue = parseInt(getModalStyle.top, 10);
                  szModalContainer.style.left = `${leftValue + movementX}px`;
                  szModalContainer.style.top = `${topValue + movementY}px`;
          };
      
          szFormHeaderBox.addEventListener('mousedown', () => {
                  szModalContainer.addEventListener('mousemove', onMouseDrag);
          });
      
          szFormFooterBox.addEventListener('mousedown', () => {
                  szModalContainer.addEventListener('mousemove', onMouseDrag);
          });
      
          document.addEventListener('mouseup', () => {
                  szModalContainer.removeEventListener('mousemove', onMouseDrag);
          });
      
          var szErrorMessages = {
          'sz-document': {
              valueMissing: 'Document is required.'
          }
          };
      
          document.addEventListener('blur', function (event) {
          var input = event.target;
          var inputId = input.id;
          var validity = input.validity;
          var connectedValidationId = input.getAttribute('aria-describedby');
          var connectedValidation = connectedValidationId ? document.getElementById(connectedValidationId) : null;
          if (connectedValidation && !input.checkValidity()) {
              var message = '';
              if (szErrorMessages[inputId]) {
                  for (var errorType in validity) {
                      if (validity[errorType] && szErrorMessages[inputId][errorType]) {
                          message = szErrorMessages[inputId][errorType];
                          break;
                      }
                  }
              }
              connectedValidation.innerText = message || input.validationMessage;
          } else if (connectedValidation) {
              connectedValidation.innerText = '';
          }
          }, true);
      
          async function checkAndCreateRecord() {
                            var apiEndpoint = '" +
      ninoxApiUrl +
      "';
                            var data = {
                             'filters': {
                                 'C': szInputs.szDocument.value
                              }
                            };
                          var headers = {
                                        'Authorization': 'Bearer " +
      ninoxApiKey +
      "',
                                        'Content-Type': 'application/json'
                          };
                          var response = await fetch(apiEndpoint, {method: 'POST',headers: headers, body: JSON.stringify(data)});
                          if (!response.ok) {
                                var message = 'HTTP status ' + response.status + ' - ' + response.statusText;
                                throw new Error(message);
                                } else {
                                     var result = await response.text();
                                    if (typeof result === 'string' && result !== '[]') {
                                    szFetchResponse.innerHTML ='Sorry, this document is already in use. Please choose a different one!';
                                     } else {
                                          data = [{
                                              fields: {
                                                       'Name': szInputs.szDocument.value,
                                                     'Vorname': szInputs.szPlan1.value,
      
                                                 }
                                          }];
                                          response = await fetch(apiEndpoint + 's', {method: 'POST',headers: headers, body: JSON.stringify(data)});
                                          if (!response.ok) {
                                                  message = 'HTTP status ' + response.status + ' - ' + response.statusText;
                                                  throw new Error(message);
                                        } else {
                                              resetAndClose();
                                               alert('The record has been saved successfully!');
                                            };
                                   };
                         };
          };
      
          function displayModal() {
              szModalContainer.style.display = 'block';
              szInputs.szFirstName.focus();
              const today = new Date().toISOString().split('T')[0];
              szInputs.szRegDate.value = today;
          };
      
          function resetAndClose() {
              szModalContainer.style.display = 'none';
              for (var key in szInputs) {
                  szInputs[key].value = '';
              };
              var szMessages = document.querySelectorAll('.sz-message');
              szMessages.forEach(function(span) {
                  span.innerText = '';
              });
          };
      
          function checkAndSave() {
              var inputs = document.querySelectorAll('input[aria-describedby]');
              var isValid = true;
              inputs.forEach(function (input) {
              var inputId = input.id;
              var validity = input.validity;
              var connectedValidationId = input.getAttribute('aria-describedby');
              var connectedValidation = connectedValidationId ? document.getElementById(connectedValidationId) : null;
              if (connectedValidation && !input.checkValidity()) {
                  var message = '';
                  if (szErrorMessages[inputId]) {
                      for (var errorType in validity) {
                          if (validity[errorType] && szErrorMessages[inputId][errorType]) {
                              message = szErrorMessages[inputId][errorType];
                              break;
                          };
                      };
                  };
                  connectedValidation.innerText = message || input.validationMessage;
                  isValid = false;
              };
              });
              if (isValid) {
                  checkAndCreateRecord().catch(error => {
                    alert('Error:  ' + error.message);
                  });
              };
          };
      
      </script>
      </body>
      ")
      
      • szormpas
      • 2 days ago
      • Reported - view

        Hi, when you put your Ninox API Key in the 'Settings' Table of the Dashboard Template Database, does it give you the same error? I mean before you make any adaptations.

    • Mel_Charles
    • 5 mths ago
    • Reported - view

    So I have read and watched this thread (and the previous one) with interest and donloaded a copy a few days ago and indeed another this morning.

    I have to say, I think you have produced something spectacularly good here. Straight off I can used several of the elements here and love the smooth scrolling of the page. The quality of the design is brilliant. I an running through your scripts to further my own education and have already played with some elements ie ( I like the analogue clock but wanted use it in a smaller size (and already reset this.)

    At the moment, I am missing how you had drawn or where the images in the top buttons were input/sourced. but will discover this in time.

    Your project shows the hidden power that could be behind Ninox if their design team developers got behind projects like this. There is not one feature on this project that is not practical and useful. Any thing I can think ok you are in place or earmarked already. I can hazard a guess at the number of hours you must have spent on this.

    ***** ABSOLUTELY BRILLIANT AND STUNNING PIECE OF WORK *****

    Regrettably - It looks like you reside in in Greece and I am in England, but if ever it per-chanced that we should ever meet. I truly would like to by you a beer! ๐Ÿ‘ 100% top job !

      • szormpas
      • 5 mths ago
      • Reported - view

        Dear Mel, thanks for the kind words. It took a lot of research and work to get this project to this point, and you've given me the chance to share a few words about it.

      My goal is to create a set of components or widgets that anyone can use in their database with just a few simple modifications.

      I'd also love to get a better understanding of Ninox's functionality to help me develop my own application. To put it another way, the "Dashboard Template" shows how I'm trying to improve myself through practice.

      In terms of the Ninox platform's future development, I don't think they need to reinvent the wheel. It'd be great to see more compatibility in integrating things like external libraries, and I'd also like to be sure that our custom code will still work in the future.

      I used to live in Newcastle for a short period, so the idea of having a pint of Guinness in a classic old English pub with you brings back fond memories! ๐Ÿ˜Š

    • Mel_Charles
    • 5 mths ago
    • Reported - view

     I did not want to spoil my praise of your work in my reply above.

    However I have 2 comments to make.

    1. Do you think it would be possible to alter the table view row height in ( know it is the same height as standard table view) but ninox allow on a table itself for the row hight to expand or contact. I wondered if on could have a control to allow a set height? - I have asked Ninix several time if this is possibe to do on view when placed on a table or page. or when placed as a child form. ie if you do a quote and it has say 12 line in the child records then that quote looks way long. Being able to bring the row height down would be great.

    So more like as shown on tables at min height

    2.  I notice that if you open the page and DO NOT press any top button but scroll down with the mouse wheel then at some point it catches either the map or floor pan and instantly starts to adjust the images height instead of carrying further down the page. I'm not suggesting that this is a fault but wondering if this is a side effect of scrolling without pressing the button to jump to each section

      • szormpas
      • 5 mths ago
      • Reported - view

        regarding your comments above:

      1. I'm not sure how we'd go about doing that right now. If we knew which Ninox CSS class is responsible for setting the row height of a View table element, it'd be pretty straightforward to change it.   might be able to help you out with that.

      2. This is more of a Ninox thing. As a general rule, if you want to use the mouse wheel to scroll down the page, it's better to set the cursor to the end right of the screen. That way, you avoid the cursor passing over a scrollable or zoomable section that would interrupt the page scrolling. We just need to get used to dealing with nested scroll bars.

      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       

      Hi Sotirios. I'm trying to adapt this but this error appears, any ideas?

      • szormpas
      • 5 mths ago
      • Reported - view

       Hi,

      To modify the code for you, I need to know the following:

      1. The 'name' of the table
      2. The 'name' and 'type' of each field you'd like to add data
      3. The 'ID' of one field for which you'd like to prevent duplicates (the widget currently supports checking for only one field).
      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       Send you the dummy database.

      • szormpas
      • 5 mths ago
      • Reported - view

        Hi, I've attached the database for you to try out.

      • Rafael Sanchis
      • Rafael_Sanchis
      • 5 mths ago
      • Reported - view

       

      Thanks Sotirios appreciate a lot your time. ๐Ÿ‘ Works perfect

    • Sean
    • 5 mths ago
    • Reported - view
     said:
    1. I'm not sure how we'd go about doing that right now. If we knew which Ninox CSS class is responsible for setting the row height of a View table element, it'd be pretty straightforward to change it.  Sean might be able to help you out with that.

    I don't want anyone getting their hopes up. I think it's more complicated than just changing some CSS.

    you can start with these classes... t-rowhead, t-row, t-selection-row-head and t-selection-row.