8

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!

198 replies

null
    • szormpas
    • 2 mths ago
    • 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! ☺️

      • Kruna
      • 2 mths ago
      • Reported - view

       Hi Sotirrios, I imported the db from scratch and it would work except when wanting to enter a new task I get this error messages

      but I do see the other task and I can open them by clicking on them..

      • szormpas
      • 2 mths ago
      • Reported - view

        Great, you get a status error: 500 which means that everything has been setup correctly except from a final configuration inside the "function createOrUpdateRecord(start, end, rid) {..."

      Replace the 'Start Time', 'End Time' and 'Id' in the following section:

                            var data = {
                          fields: {
                                 'Start Time': start,
                                 'End Time': end
                             },
                          ...(rid && { 'Id': rid })
                            };
      

      with the names of your fields ('erstellt am', 'Fällig am', 'Nr') like below:

                            var data = {
                          fields: {
                                 'erstellt am': start,
                                 'Fällig am': end
                             },
                          ...(rid && { 'Nr': rid })
                            };
      

      Let me know if everything is working now.

      • Kruna
      • 2 mths ago
      • Reported - view

       you rock!!!👍😀 that did it - tons of thank you!!! now everything works perfect.

      I have two questions left please.

      I tried to translate here

        headerToolbar: {
                                  left: 'prev,next today',
                                  center: 'title',
                                  right: 'dayGridMonth timeGridWeek timeGridDay'
                                 },
      

           to    left: 'vor,nach heute',

      but its left empty. Is there a way to translate? I was looking at the script, but couldnt find another piece of code and the other question is whether it is possible to start week on monday insted of sunday?

      Thank you so much for your great help!
       

      • szormpas
      • 2 mths ago
      • Reported - view

        yes, the Full Calendar library supports many languages.

      To translate the Scheduler to German you should do the following.

      First, just below the <script> ..."Full Calendar js library v6.1.15"...</script> insert the following 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>
      

      Then, you should add an extra option (locale: 'de') inside the full calendar instance object like below:

              var calendar = new FullCalendar.Calendar(calendarEl, {
                  locale: 'de',
                  height: '100%',
                  ...
      • szormpas
      • 2 mths ago
      • Reported - view

        another cool thing is that if you set the locale to 'de', it automatically accepts 'Monday (Montag)' as the first day of the week! 😉

      • Kruna
      • 2 mths ago
      • Reported - view

       yes I noticed that cool thing too 😁 and therefore I managed to make it work. THANKS!!!

      Yet I am facing another challenging issue for me. I have task and appointments, therefore I have a yes/no field for wheather it is a task or appointment. For task I have two date fields and two hour fields seperated. The first date and hour field is for when the task is made the second when its due. For apppointment I have the appointment field. I need those fields to show in ninox calendar as it syncronizes with Apple iCal. The date+hour field in this awesome calendar wont show up in ninox calendar.

      I tried 

      start: format('erstellt am', "DD-MM-YYYYTHH:mm"),
      end: if 'als Termin festlegen' then
      format(Termin, "YYYY-MM-DDTHH:mm")
      else
      format('Fällig am', "YYYY-MM-DDTHH:mm")
      end,
      

      als Termin festlegen is the yes/no field Termin is the appointment field and Fällig am is the date field and hour field seperated. So basically what I am trying to achieve

      if yes/no field checked then appoinment field else date and hour field. Do you have an idea, if this is possible at first place?😅

      • szormpas
      • 2 mths ago
      • Reported - view

        If I've got this right, you're looking for a way to pull out the start and end datetimes from an 'Appointment' field.

      For the start you can use this:

      format(start(Termin), "YYYY-MM-DDTHH:mm")
      

      While for the end you can use this:

      format(endof(Termin), "YYYY-MM-DDTHH:mm")
      
      • Rafael Sanchis
      • Rafael_Sanchis
      • 2 mths ago
      • Reported - view

       

      With the slotMinTime and slotMaxTime you can adjust the day time.

      initialDate: savedDate,
                  slotDuration: '00:30:00',
                              slotMinTime: '08:00:00',
                              slotMaxTime: '18:00:00',
      
      • Kruna
      • 2 mths ago
      • Reported - view

       yes, you are right and that works perfect now!👍 thank you so much for helping me sort out this issue.

      Next and hopefully last I am trying to resolve here is the yes/no field issue - the code is

      start: format(start(Termin), "YYYY-MM-DDTHH:mm"),
      end: if 'als Termin festlegen' then
                  format(endof(Termin), "YYYY-MM-DDTHH:mm")
              else
                  format('Fällig am', "YYYY-MM-DDTHH:mm")
              end,
      

      It doesnt show any error, but doesnt work at all. It seems not to be as easy as I thought 😅, but I am not sure wheather its possible at first place?

      • Kruna
      • 2 mths ago
      • Reported - view

       muchas gracias Rafael, when I insert your script it shows me errors 😅

      Edit: I put it in the wrong lines, now its correct and a good point!! Thank you for the helpful hint👍

      If I put 18:00 it just shows till 17:00 and 18:00 its not clickable - is it supposed to be like that?

      • szormpas
      • 2 mths ago
      • Reported - view

        Hi,

      Your code looks good to me. I tried it in a similar situation and it worked fine.

      One thing to look at is the field values, which shouldn't be null.

      Both the 'YES/NO' field and the corresponding datetime field need a value, otherwise the scheduler won't display the event.

      • Kruna
      • 2 mths ago
      • Reported - view

       good morning - it somehow doesnt work for me. The appointments are shown however the date field not. I also tried to use a choice field 

      start: format(start(Termin), "YYYY-MM-DDTHH:mm"),
              end: switch Auswahl do
              case 1:
                  format('Fällig am', "YYYY-MM-DDTHH:mm")
              case 2:
                  format(endof(Termin), "YYYY-MM-DDTHH:mm")
              end,
      

      but case 1 doesnt work. :-(

      • szormpas
      • 2 mths ago
      • Reported - view

       good morning. What kind of field is the 'Falling am', it should be a date/time field and not a plain Date field. Otherwise, you should combine a Date and a Time field.

      • Kruna
      • 2 mths ago
      • Reported - view

       its only a date field the hour field is seperate. The reason why I have them seperated is that combine date/time field doesnt show in ninox calender. I need all fields with dates being showed in ninox calender too as its synchronized with iCal - the apple calendar. By the way is there a way to sync FullCalendar with iCal?

      I tried this without success...

      end: switch Auswahl do
              case 1:
                  format('Fällig am', "YYYY-MM-DD") + "" + format(Uhrzeit, "THH:mm")
              case 2:
                  format(endof(Termin), "YYYY-MM-DDTHH:mm")
              end,
      

      thanks

      • szormpas
      • 2 mths ago
      • Reported - view

        try the following and let me know if everything is working as expected:

      format(datetime('Fällig am', 'Uhrzeit'), "YYYY-MM-DDTHH:mm")
      
      • szormpas
      • 2 mths ago
      • Reported - view

      At first glance, I see that the Full Calendar library can display events from a public iCalendar feed, according to the docs: https://fullcalendar.io/docs/icalendar

      • Kruna
      • 2 mths ago
      • Reported - view

       thank you so much for all your help and time, but this doesnt work either. I feel that the error might be somewhere else and not in the script. 

      • Kruna
      • 2 mths ago
      • Reported - view

       I mean the other way round, like that Full calendar can be exported to iCal. Googleing around there are some approches, but no way for me to implement it - its far beyond my knowledge. 😅

      • szormpas
      • 2 mths ago
      • Reported - view

        Hi, to figure out what's going on with your setup, we'll need to go back to the beginning and work through it step by step.
      For instance, you could start by using the Dashboard Template to simulate the structure of your database, and then build it up gradually.

      • Kruna
      • 2 mths ago
      • Reported - view

       yes, you are absolutely right. This is what I am going to do.

      Insert your code from scratch and the do the respective adaptions. I'll let you know, if I succeeded.🙂

      Thanks a lot for taking your time 👍🙂

      • Kruna
      • 1 mth ago
      • Reported - view

       hi, I have two issues I somehow dont get resolved please.

      I've added a choice-field in appointments and I would like to change color when choicefield =1 .

      text(_events) +
          ",
                               color: '#FFA334',
                               textColor: 'white'
                               }]  ,
                    eventClick: function(info) {
                          ui.popupRecord('"
      +
          tableId +
      

      I started with something like 

      if choicefield =1 then
      text(_events) +
          ",
                               color: '#FFA334',
                               textColor: 'white'
                               }]  ,
                    eventClick: function(info) {
                          ui.popupRecord('"
      +
      else text(_events) +
          ",
                               color: '#FF0033',
                               textColor: 'white'
                               }]  ,
                    eventClick: function(info) {
                          ui.popupRecord('"
      +
      

      but it doesnt work. I also tried to put in another line, but without success.

      The other issue would be that each Account name should have its own color. I assume that I can add a color filed and assign a color to each Name. How can I implement that text(_events) uses the color assigned to each account name? Thanks a lot :-)

      • szormpas
      • 1 mth ago
      • 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
      • 1 mth ago
      • 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
      • 1 mth ago
      • 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
      • 1 mth ago
      • 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 

Content aside

  • 8 Likes
  • 3 days agoLast active
  • 198Replies
  • 2235Views
  • 15 Following