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
            • 7 mths agoThu, September 5, 2024 at 11:31 PM UTC
            • Reported - view

            Version 8.0

            Calendar (scheduler):

            Hello everyone,

            As we all know, Ninox's built-in calendar is pretty basic and has limited capabilities. I thought that if we integrated a custom calendar directly within the dashboard, we could add features that would enhance the user experience.

            FullCalendar.io is a JavaScript library that provides its core as open source. The good news is that its code seems to be quite compatible with Ninox. I was able to fully integrate it and store it inside the Ninox database (no dependency on CDN links).

            I've been able to develop the following features so far:

            • You can upload and display data directly from the Ninox database (event sources).
            • When you hover the mouse over each event, a tooltip will appear (thanks to the hint.css library).
            • When you click on an event, the corresponding record will pop up.
            • To schedule a new event, just choose one or more free slots in the calendar. A new record will be created automatically and populated with the corresponding dates (thanks to  for sharing the use of the fetch method in this post).
            • If you need to change the time of an event, just drag and drop it into a different time slot. The record in the Ninox database will be updated right away.
            • You can edit the events on the calendar, which means you can move and resize their start and/or end times.
            • I've added three views (month, week, day), and I've got more in the pipeline!

            I'm thrilled to announce the launch of the first free, open-source scheduler for Ninox! ☺️

              • szormpas
              • 5 mths agoSun, October 13, 2024 at 2:42 PM UTC
              • Reported - view

                Hi,

              I think the best way forward is to make use of Full Calendar's amazing ability to handle multiple event sources.

              Your code should look like this:

              eventSources: [{
                               events: " + text(_events01) + ",
                               color: '#3485FF',
                               textColor: 'white'
                             },
                             {
                               events: " + text(_events02) + ",
                               color: '#3485FF',
                               textColor: 'white'
                             }]
              

              In the same way, you could create multiple event objects like this:

              let x := (select Appointments);
              let _events01 := x[choicefield=1].{
                                                //some code here
                                                };
              let _events02 := x[choicefield=0].{
                                                //some code here
                                                };
              

              Of course, you can adjust each event's background and text color as you like it!

              • szormpas
              • 5 mths agoSun, October 13, 2024 at 4:35 PM UTC
              • Reported - view

                

              Following the above, If you want each user account to have a different colour and also to be able to change the colour based on a choice field, you could try something like this:

              let x := (select Appointments);
              let _eventsUser01 := x[text(_cu)="User01"].{
                                                          backgroundColor: if 'choice field'=1 then "#3485FF" end,
                                                          borderColor: if 'choice field'=1 then "#73455FF" end,
                                                          textColor: if 'choice field'=1 then "#6578FF" end,
                                                          //some code here
                                                          };
              let _eventsUser02 := x[text(_cu)="User02"].{
                                                          backgroundColor: if 'choice field'=1 then "#3485FF" end,
                                                          borderColor: if 'choice field'=1 then "#73455FF" end,
                                                          textColor: if 'choice field'=1 then "#6578FF" end,
                                                          //some code here
                                                          };
              
              • szormpas
              • 5 mths agoSun, October 13, 2024 at 4:58 PM UTC
              • Reported - view

                to quickly recap, you can construct multiple event sources. You can do this for each user account, and you can distinguish them by a different background and text colour.

              eventSources: [{
                               events: " + text(_eventsUser01) + ",
                               color: '#3485FF',
                               textColor: 'white'
                             },
                             {
                               events: " + text(_events02) + ",
                               color: '#5687FF',
                               textColor: 'red'
                             }]
              
              

              Also, you can override the default colors of each event source based on e.g. a 'choice field':

              let x := (select Appointments);
              let _eventsUser01 := x[text(_cu)="User01"].{
                                                  backgroundColor: if 'choice field'=1 then "#3485FF" end,
                                                  borderColor: if 'choice field'=1 then "#73455FF" end,
                                                  textColor: if 'choice field'=1 then "#6578FF" end,
                                                  //some code here
                                                  };
              

              I really hope this all makes sense to you!

              • Kruna
              • 5 mths agoMon, October 14, 2024 at 5:11 AM UTC
              • Reported - view

               good morning Sotorios - unfortunately it's all Greek to me😅

              I tried to adapt the above scripts, but I dont get it to work, plus I noticed that I explained my issue wrong. When I wrote aboutAccount user I meant the "user" from your table 'Accounts' like First name, Last name.etc. In my case its the table 'Employee'. So its not about the ninox database user, but about an employee from table.😅 I wish I could assign to each employee a different color plus when there is a choice another color too. I hope my mess-up makes sense to you😄

              This is my complete script

              let ninoxApiKey := (select Settings)[Name = "Ninox API Key"].Value;
              let teamId := teamId();
              let databaseId := databaseId();
              let tableId := tableId("Terminvergabe");
              let ninoxApiUrl := "https://api.ninox.com/v1/teams/" + teamId + "/databases/" + databaseId +
                  "/tables/" +
                  tableId +
                  "/records";
              let x := (select Terminvergabe);
              let _events := x.{
                      id: number(Nr),
                      title: Mitarbeiter.Vorname + " - " + Kunde.Nachname + ", " + Kunde.Vorname,
                      start: format(Terminbeginn, "YYYY-MM-DDTHH:mm"),
                      end: format(Terminende, "YYYY-MM-DDTHH:mm"),
                      extendedProps: {
                          description: Terminpositionen.Dienstleistung
                      }
                  };
              html("<head>
              <style>" +
              Sources[Name = "Hint CSS library  v3.0.0"].Source +
              "</style>
              <style>
                   .fc .fc-button-primary {
                           color: #FFFFF;
                           background-color: #5c6275 !important;
                            border-color: #FFFFFF !important;
                  }
                  [class*=hint--]:after {
                           background: white;
                           color: black;
                           font-size: 14px;
                           text-shadow: none;
              
                           border: 1px solid #008c99;
                  }
                  [class*=hint--]:before {
                           background-color: #008c99;
                  }
              </style>
              <script>" +
              Sources[Name = "Full Calendar js library v6.1.15"].Source +
              "</script>
              <script>
              !function(e){'use strict';function t(e){return'Tag'===e||'Monat'===e?'r':'Jahr'===e?'s':''}var n={code:'de',week:{dow:1,doy:4},buttonText:{prev:'Zurück',next:'Vor',today:'Heute',year:'Jahr',month:'Monat',week:'Woche',day:'Tag',list:'Terminübersicht'},weekText:'KW',weekTextLong:'Woche',allDayText:'Ganztägig',moreLinkText:e=>'+ weitere '+e,noEventsText:'Keine Ereignisse anzuzeigen',buttonHints:{prev:e=>`Vorherige${t(e)} ${e}`,next:e=>`Nächste${t(e)} ${e}`,today:e=>'Tag'===e?'Heute':`Diese${t(e)} ${e}`},viewHint:e=>e+('Woche'===e?'n':'Monat'===e?'s':'es')+'ansicht',navLinkHint:'Gehe zu $0',moreLinkHint:e=>'Zeige '+(1===e?'ein weiteres Ereignis':e+' weitere Ereignisse'),closeHint:'Schließen',timeHint:'Uhrzeit',eventHint:'Ereignis'};FullCalendar.globalLocales.push(n)}();
              </script>
              
              </head>
              <body>
                  <div id='calendar'></div>
                  <script>
                      var calendarEl = document.getElementById('calendar');
                      var savedView = sessionStorage.getItem('calendarView') || 'timeGridWeek';
                      var savedDate = sessionStorage.getItem('calendarDate') || new Date();
                      var calendar = new FullCalendar.Calendar(calendarEl, {
                          locale: 'de',
              
                          height: '100%',
                          initialView: savedView,
                          initialDate: savedDate,
                          slotDuration: '00:30',
                          slotMinTime: '07:00:00',
                          slotMaxTime: '20:00:00',
                          headerToolbar: {
                                          left: 'prev,next today',
                                          center: 'title',
                                          right: 'dayGridMonth timeGridWeek timeGridDay'
                                         },
                          buttonIcons: false,
                          selectable: true,
                          editable: true,
                          eventResizableFromStart: true,
                          eventOverlap: false,
                          allDaySlot: false,
                          dayMaxEventRows: 0,
                          dayMaxEvents: 0,
                          datesSet: function (dateInfo) {
                                 sessionStorage.setItem('calendarView', dateInfo.view.type);
                                 sessionStorage.setItem('calendarDate', dateInfo.startStr);
                          },
                          eventTimeFormat: {
                                 hour: 'numeric',
                                 minute: '2-digit',
                                 meridiem: 'short'
                          },
                          eventSources: [{
                                        events: " +
              text(_events) +
              ",
                                       color: '#008c99',
                                       textColor: 'white'
                                       }],
                            eventClick: function(info) {
                                  ui.popupRecord('" +
              tableId +
              "' + info.event.id);
                          },
                            select: function(info) {
                                  createOrUpdateRecord(info.startStr, info.endStr);
                          },
                          eventDrop: function(info) {
                                  createOrUpdateRecord(info.event.startStr, info.event.endStr, info.oldEvent.id);
                          },
                          eventResize: function(info) {
                                  createOrUpdateRecord(info.event.startStr, info.event.endStr, info.oldEvent.id);
                          },
                          eventClassNames: function(info) {
                                  var isWeekView = info.view.type === 'timeGridWeek';
                                  var classes = ['hint--top', 'hint--no-animate', 'hint--rounded'];
                                    if (isWeekView) {
                                          classes.push('hint--fit');
                                    } else {
                                          classes.push('hint--large');
                                    }
                                    return classes;
                             },
                          eventDidMount: function(info) {
                                    info.el.setAttribute('aria-label', info.event.extendedProps.description);
                          }
                      });
                          calendar.render();
                          function createOrUpdateRecord(start, end, rid) {
                        var apiEndpoint = '" +
              ninoxApiUrl +
              "';
                    var data = {
                      fields: {
                         Terminbeginn: start,
                        Terminende: end
                      },
                      ...(rid && { 'id': rid })
                    };
                    fetch(apiEndpoint, {
                      method: 'POST',
                      headers: {
                        'Authorization': 'Bearer " +
              ninoxApiKey +
              "',
                        'Content-Type': 'application/json'
                      },
                      body: JSON.stringify(data)
                    })
                    .then(response => {
                      if (response.ok) {
                          return response.json();
                      }
                      else {
                              alert('HTTP status error: ' + response.status + ' - ' + response.statusText);
                      }
                      })
                    .then(result => {
                              if (rid === undefined) {
                                  ui.popupRecord('" +
              tableId +
              "' + result.id);
                              } else {
                                    alert('The appointment has been successfully rescheduled!');
                              }
                    })
                    .catch(error => {
                      alert('Promise rejected: ' + error.message);
                    });
              };
              </script>
              </body>")
              

              I found your piece of codes, but I was not able to add you scripts correctly.

              thank you 

              • Kruna
              • 5 mths agoMon, October 14, 2024 at 12:13 PM UTC
              • Reported - view

               yessssss💪, I managed to assign color for choice field like you described here

              let x := (select Appointments); let _events01 := x[choicefield=1].{ //some code here   }; let _events02 := x[choicefield=0].{ //some code here   };
              

              Now, how can I assign color by employees?

              thnx a lot🙂

              • szormpas
              • 5 mths agoMon, October 14, 2024 at 9:00 PM UTC
              • Reported - view

                Congrats, I'm happy to say that it's not as difficult as you might think to understand Greek! 😉

              How many employees does your table have?

              • Kruna
              • 5 mths agoTue, October 15, 2024 at 3:41 AM UTC
              • Reported - view

               Good morning 🙂 thank you very much. The docs from fullcalendar with respective demos and last but not least learning by doing helped a bit to understand some more to understand - Greek.😄

              For now there are only two employees, but maybe I will have to use two records for each employee, so there could be four 'colors' - maybe more to come.

              thnx

              • szormpas
              • 5 mths agoTue, October 15, 2024 at 9:39 PM UTC
              • Reported - view

               Hi, four colors is not too many, so you can follow the same logic and create four unique event sources and then assign a different color to each.

              • Kruna
              • 5 mths agoWed, October 16, 2024 at 5:44 AM UTC
              • Reported - view

               me and logic...😂 Well here I used the unique events based on the choice field and I have only two choices, so I am not sure how to create other four? I dont know how to achieve this. My first approach ist that each employee has a record ID.

              In one of your previous suggestions you wrote

              let x := (select Appointments);
              let _eventsUser01 := x[text(_cu)="User01"].{
              backgroundColor: if 'choice field'=1 then "#3485FF" end,
              borderColor: if 'choice field'=1 then "#73455FF" end,
              textColor: if 'choice field'=1 then "#6578FF" end,
              //some code here
               };
              

              So, is there a way to - lets say -

              let _eventsUser01 := x[Employee_record_id=1].{  
               

              thnx

              • szormpas
              • 5 mths agoWed, October 16, 2024 at 9:18 PM UTC
              • Reported - view

                Hi, you're right, you need to create an event source for each employee. This means filtering the appointments per employee. How you do this depends on how your database is set up, especially the relationship between the employee table and the appointment table. Can you describe this relationship?

              • Kruna
              • 5 mths agoThu, October 17, 2024 at 6:44 AM UTC
              • Reported - view

               I have linked the employee table with the appointment table - here is a screenshot

              thnx

              • Kruna
              • 5 mths agoThu, October 17, 2024 at 1:13 PM UTC
              • Reported - view

               please excuse me, but I have another question. I was trying to add another calender. Right under the existing one, but the formula field is empty. I suppose it has to do with html. So I put it in Tab 2 and it worked, but the calendar is absolutely the same, which I do retrace. I guess there must be something like calendar2, right?

              I just cant figure out how to achieve this. Can you help me please, do you have an idea how this works?

              TY :-)

              • szormpas
              • 5 mths agoThu, October 17, 2024 at 10:02 PM UTC
              • Reported - view

                Hi, you are right.

              You should create a new <div> element with a unique Id and replace the var names accordingly:

              ...
              <body>
                  <div id='calendar2'></div>
                  <script>
                      var calendar2El = document.getElementById('calendar2');
                      var savedView2 = sessionStorage.getItem('calendar2View') || 'timeGridWeek';
                      var savedDate2 = sessionStorage.getItem('calendar2Date') || new Date();
                      var calendar2 = new FullCalendar.Calendar(calendar2El, {
              ...
              

              You don't have to reload the CSS style or the Full Calendar library twice!

              • szormpas
              • 5 mths agoThu, October 17, 2024 at 10:30 PM UTC
              • Reported - view

                Hi, try the following:

              let x := (select Mitarbeiter);
              let _eventsEmployee01 := x[Nr=1].Terminvergabe.{
              backgroundColor: if 'choice field'=1 then "#3485FF" end,
              borderColor: if 'choice field'=1 then "#73455FF" end,
              textColor: if 'choice field'=1 then "#6578FF" end,
              //some code here
               };
              
              • Kruna
              • 5 mths agoFri, October 18, 2024 at 9:24 AM UTC
              • Reported - view

               omg!!! thats so awesome!!!thank you very much for all your patience and help.👍 Now I will have to find the script pieces for CSS.😅

              • Kruna
              • 5 mths agoFri, October 18, 2024 at 10:13 AM UTC
              • Reported - view

               this is too tricky for me😅 I managed to add your code. At first the formula field was empty. Then I found out that there are other three lines where I needed to change from calendar to calendar2. The fullcalendar showed up, but its still the same calendar as calendar1.

              • szormpas
              • 5 mths agoFri, October 18, 2024 at 10:17 AM UTC
              • Reported - view

                have you use a different event source for the calendar2 ?

              • Kruna
              • 5 mths agoFri, October 18, 2024 at 10:34 AM UTC
              • Reported - view

               omg, I feel so dumb! Yes, now I used different event sources and it works as it should. Thanks so much :-)))

              • szormpas
              • 5 mths agoFri, October 18, 2024 at 10:59 AM UTC
              • Reported - view

                you nailed it! I'm with you on the multiple calendars feature. It's a great addition.

              • Kruna
              • 5 mths agoFri, October 18, 2024 at 11:09 AM UTC
              • Reported - view

               yes definetly!!! Thank you so much for that. Now every employee has its own calendar.

              Another issue(s) I found useful to add and I would like to share 

              1. calendar week,

              2. a line which shows the actual time and

              3. drag the appointments/events in calender.

              headerToolbar: {
                                          left: 'prev,next today',
                                          center: 'title',
                                          right: 'dayGridMonth timeGridWeek timeGridDay listMonth'
                                         },
                          weekNumbers: true,
                          nowIndicator: true,
                          droppable: true,
            • szormpas
            • 7 mths agoSun, September 8, 2024 at 3:14 AM UTC
            • Reported - view

            Version 8.1

            Floor plan:

            Hello everyone

            came up with this idea in a post, so I thought I'd add it to the dashboard as it might be useful for other users too.

            The Leaflet library lets us turn any image or photo into a map surface.

            Once we know the dimensions of the image, we can easily convert them to coordinates with a 1:1 ratio and use all the great features that Leaflet offers (such as markers, shapes, popups, zoom in/out, events on click, and more).

            I'm using a floor plan as an example here. When you move the mouse over each room, it highlights in colour. Click on it and a pop-up containing a photo of the room appears.

            Have fun!

              • cyan_silver
              • 5 mths agoMon, October 14, 2024 at 5:34 AM UTC
              • Reported - view

               
              thank you

              • cyan_silver
              • 5 mths agoThu, November 7, 2024 at 9:16 AM UTC
              • Reported - view

               
               

              Is there any solution if I use IPAD offline?
            • szormpas
            • 7 mths agoSun, September 8, 2024 at 11:21 PM UTC
            • 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! 😂

             

              • Fred
              • 7 mths agoMon, September 9, 2024 at 4:07 AM UTC
              • Reported - view

              You are da Man!