0

I have a PDF form with 6 different time periods for each day of the month and I need to make sure those time periods do not overlap when the user completes them. Not all time periods may be completed, but the ones that do must be unique. I have taken inspiration from this previous post Use JavaScript to compare time ranges specifically the contribution by @RobG. However, my lack of scripting knowledge is hampering my ability to mold that code for my specific application. In addition to have the time periods verified, I also have to repeat this for every day on the form but I would like to do it one day at a time particularly when the entries are made for that day. Since this is a PDF form, I am not certain how to execute that validation. For example, should it execute every time a time period is completed? That is, have a Start Time and and End Time? So it would act as a "validation" script for that time entry? Here is a sample of what the form looks like:

enter image description here

enter image description here

Here is my attempt at updating the code for my form:

var TimeIn1 = this.getField("Day1Pd1TimeInRes").value;
var TimeOut1 = this.getField("Day1Pd1TimeOutRes").value;
var TimeIn2 = this.getField("Day1Pd2TimeInRes").value;
var TimeOut2 =  this.getField("Day1Pd2TimeOutRes").value;
var TimeIn3 = this.getField("Day1Pd3TimeInRes").value;
var TimeOut3 = this.getField("Day1Pd3TimeOutRes").value;
var TimeIn4 = this.getField("Day1Pd4TimeInRes").value;
var TimeOut4 = this.getField("Day1Pd4TimeOutRes").value;
var TimeIn5 = this.getField("Day1Pd5TimeInRes").value;
var TimeOut5 =  this.getField("Day1Pd5TimeOutRes").value;
var TimeIn6 = this.getField("Day1Pd6TimeInRes").value;
var TimeOut6 = this.getField("Day1Pd6TimeOutRes").value;

var timeSlots = [
  {BeginTime: new Date("10/25/2015 " + TimeIn1),
   EndTime: new Date("10/25/2015 " + TimeOut1)},
  {BeginTime: new Date("10/25/2015 " + TimeIn2),
   EndTime: new Date("10/25/2015 " + TimeOut2)},
  {BeginTime: new Date("10/25/2015 " + TimeIn3),
   EndTime: new Date("10/25/2015 " + TimeOut3)},
  {BeginTime: new Date("10/25/2015 " + TimeIn4),
   EndTime: new Date("10/25/2015 " + TimeOut4)},
  {BeginTime: new Date("10/25/2015 " + TimeIn5),
   EndTime: new Date("10/25/2015 " + TimeOut5)},
  {BeginTime: new Date("10/25/2015 " + TimeIn6),
   EndTime: new Date("10/25/2015 " + TimeOut6)}
];
function slotFits(slot) {
  if (timeSlots.length == 0) return true; // If no slots, must fit
  return timeSlots.some(function(s, i) {
    return (i == 0 && slot.EndTime <= s.BeginTime) || //slot is before all others
  (s.EndTime <= slot.BeginTime && !timeSlots[i + 1]) || //slot is after all others
  (s.EndTime <= slot.BeginTime && timeSlots[i + 1].BeginTime >= slot.EndTime) // Slot between others
  });
}

[{BeginTime: new Date("10/25/2015 " + TimeIn1),
  EndTime: new Date("10/25/2015 " + TimeOut1)},
 {BeginTime: new Date("10/25/2015 " + TimeIn2),
  EndTime: new Date("10/25/2015 " + TimeOut2)},
 {BeginTime: new Date("10/25/2015 " + TimeIn3),
  EndTime: new Date("10/25/2015 " + TimeOut3)},
 {BeginTime: new Date("10/25/2015 " + TimeIn4),
  EndTime: new Date("10/25/2015 " + TimeOut4)},
 {BeginTime: new Date("10/25/2015 " + TimeIn5),
  EndTime: new Date("10/25/2015 " + TimeOut5)},
 {BeginTime: new Date("10/25/2015 " + TimeIn6),
  EndTime: new Date("10/25/2015 " + TimeOut6)},
].forEach(function(slot, i) {
  app.alert('Slot ' + i + ': ' + slotFits(slot));
});

In this sample I spelled out the field names, but because I will be repeating this for up to 31 days at a time, I would like to find a way to create a document script where I can put in the 'TimeIn' and 'TimeOut variables and call them for each day, but I think I can get away without that if necessary, just concerned about form size repeating that much code for 6 periods for potentially 31 days. Thank you!

Edit: Time values are HH:mm

  • On a programming note: there is so much repetition-with-just-numbers-differing, why extract everything into its own variable instead of just extracting to an array or object with a loop that iterates over those numbers? It would easily get rid of 20 lines of code here – Mike 'Pomax' Kamermans Jun 28 '18 at 19:05
  • @Mike 'Pomax' Kamermans I am open to suggestions! I am just not sure how to go about doing that. – Bobby Jones Jun 28 '18 at 19:13
  • honestly, I'd use a nicely readable and predictable class. Let me whip up an example. (Also, *never* use `alert`. We've had the `console` API since 2010, use `console.log` and its related functions, then read your dev tools console) – Mike 'Pomax' Kamermans Jun 28 '18 at 19:18
  • @Mike 'Pomax' Kamermans The JavaScript console in this PDF program does not seem to support `console.log`. I was using `app.alert` because in the end the user needs to be alerted to the issue. I have found this JavaScript console to be very interesting with very subtle differences when it comes to executing some JavaScript. But I am no master, that is for sure. I appreciate your assistance. Thank you. – Bobby Jones Jun 28 '18 at 19:26
  • @Mike 'Pomax' Kamermans I have found that `console.printIn` seems to operate like `console.log`. – Bobby Jones Jun 28 '18 at 19:29
  • If foxit allows reasonably modern JS, something like https://hastebin.com/genohahoxo.js would make a lot of sense. – Mike 'Pomax' Kamermans Jun 28 '18 at 20:01
  • @Mike 'Pomax' Kamermans That code looks fabulous and makes a lot of sense. However, Foxit chokes on the first line `[ Line: 00000 { SyntaxError } ] : Unexpected reserved word` – Bobby Jones Jun 28 '18 at 20:27
  • I guess Foxit uses 1998 JavaScript. You have my sympathies. Let me rewrite this to ancient JS form as an answer, instead. – Mike 'Pomax' Kamermans Jun 28 '18 at 23:01

1 Answers1

0

Let's rewrite this a little, because the typically solution to "things are getting weirdly complicated" is typically "pick a better data structure", which in this case means using an Entry representation, and making that do the work for you.

Given that Foxit apparently does not support modern JS, using older ES5 this would look a little something like:

function Entry(pdfObject, day, pd, dateOffset) {
  this.id = day + '-' + pd;
  this.in = pdfObject.getField('Day'+day+'Pd'+pd+'TimeInRes').value;
  this.out = pdfObject.getField('Day'+day+'Pd'+pd+'TimeOutRes').value;
  this.begin = new Date(dateOffset+' '+this.in);
  this.end = new Date(dateOffset+' '+this.out);
  this.interval = this.end.getTime() - this.begin.getTime(); 
}

Entry.prototype = {
  checkFit: function checkFit(entries) {
    // 1: find out where our start fits into the list of start times
    var start = -1, l = entries.length, other, i, e;
    for (i=0; i<l; i++) {
      e = entries[i];
      if (this.begin < e.begin) {
        start = i;
        other = e;
      }
      if (this.begin > e.begin) break;
    }
    // are we the last entry? if so, we fit if we start after the last end.
    if (!other || start === l-1) return (this.begin > entries[l-1].end);
    // IF not, there is a next entry; do we end before that begins?
    return other.begin <= this.end;
  }
}

Entry.loadAll = function loadAll(pdfObject, day, dateOffset) {
  return [1,2,3,4,5,6]
         .map(function(i) { return new Entry(pdfObject, day, i, dateOffset); })
         .sort(function(a,b) { return a.end - b.end; });
}

And we can then make use of this object using code like:

// Make sure we have a reference to the pdf API so we can getField etc.
var pdfObject = this;

// I'm assuming the day and date offset should not be hardcoded
var entries = Entry.loadAll(pdfObject, 1, "10/25/2015");

// check the entries
entries.forEach( function(entry) {
  let fits = entry.checkFit(entries);
  console.log('Slot check for entry '+entry.id+': '+fits);
});
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • Thank you for this! I have been out of the office and have not had the opportunity to try it, but I will and then let you know how it goes. Thank you again for taking the time to share your expertise with me. – Bobby Jones Jun 29 '18 at 19:16
  • Thank you once again for the time and effort you put into this for me. Unfortunately, it does not work. Console is reporting that `let` is `{SyntacError}:Unexpected identifier`. Additionally, it is saying that `{ReferenceError}: pdfObject is not defined`. :-( – Bobby Jones Jul 02 '18 at 21:43
  • yes. Replace `let` with `var`, and `pdfObject` is your pdf object. I have no idea what the handle to that is called in your script. – Mike 'Pomax' Kamermans Jul 03 '18 at 03:16
  • I am not completely clear what `pdfObject` should be. When I put in a fieldname for `pdfObject` I get `[ Line: 00002 { TypeError } ] : undefined is not a function`. If I am reading the script carefully, you are generating my `Day1Pd1TimeInRes`, `Day1Pd2TimeInRes` and so on when you call for values, so what am I missing? Thanks. – Bobby Jones Jul 03 '18 at 20:43
  • `pdfObject` is the thing that you would otherwise call `.getField(...)` on. I assumed it was some actual variable, rather than `getField()` being a global call. – Mike 'Pomax' Kamermans Jul 03 '18 at 22:02
  • I think I understand, but I do not have another variable to call that I can determine. Every period is comprised of a simple "In" and "Out". I determine the time difference between the "In" and "Outs" but that would not help me here I am thinking. – Bobby Jones Jul 03 '18 at 22:09
  • your original question uses `this.getField`, so just put `this` as value for the `pdfObject` parameter when you call `Entry.loadAll`, so that you get `var entries = Entry.loadAll(this, 1, "10/25/2015")`. – Mike 'Pomax' Kamermans Jul 03 '18 at 22:41
  • I am so sorry, Mike, I just do not understand. I really want to, but I don't and I am very frustrated with myself. So here is where I am at. I put the main function `Entry` as a document level script in my PDF document. Then I call that function using your "make use of object code" example above where? And I still do not know what field I need to call to execute the script. I added an additional image above. – Bobby Jones Jul 05 '18 at 17:28
  • In your original script, you use `this.getField(...)`, so in your script, in the scope where you have those lines, `this` refers to the PDF object. It is "the thing" that you call `.getField(...)` on. So, first capture that as a real, named variable: `var pdfObject = this;` so that we can pass that around safely, and then call `var entries = Entry.loadAll(pdfObject, 1, "10/25/2015");` which will now have a real, and correct, reference to the object that represents the pdf file. – Mike 'Pomax' Kamermans Jul 05 '18 at 18:05
  • I so much appreciate your time and effort, but I just don't know what I am doing. Without an exact example I am lost. I know enough for basic functions, but my level of understanding here is out the window. This program/form works in a specific way and unless I can see how this solution fits that mold, I don't know what to do with it. Of course the fact that this form uses some old school javascript and I have not coded in javascript for over 10 years, I am beside myself. Having said that, I do not wish to frustrate you. I appreciate all you have done for me. – Bobby Jones Jul 05 '18 at 19:50
  • I literally told you, word for word, the exact example. Look at your code now. Delete _all of that_, and replace it with the code from this answer. I've updated it to capture that pdf object. Both code blocks in the answer go in your file, in the order shown, and completely replace what you currently have. That _should_ work. If not, something very strange is going on. – Mike 'Pomax' Kamermans Jul 05 '18 at 20:20
  • The first error I get is `[ Line: 00001 { ReferenceError } ] : da is not defined`. Simple enough, `da` should be `day` fixed. Next error I get is `[ Line: 00024 { TypeError } ] : Cannot read property 'begin' of undefined`. Line 00024 is `return other.begin <= this.end;` What am I missing? This also assumes I have replaced `let` with `var` in all occurrences. – Bobby Jones Jul 05 '18 at 21:46
  • fair enough, let me work in those changes too. The TypeError is more serious though, it suggests that somehow `other` isn't a real entry, let me see what might be happening there. – Mike 'Pomax' Kamermans Jul 05 '18 at 22:01
  • added a check to make sure to do the right thing when `other` hasn't been assigned during the for loop. – Mike 'Pomax' Kamermans Jul 05 '18 at 22:05
  • I had an error that `slots` was not defined. However, I am assuming `slots.id` should be `this.id`. After changing that, the script results are `Slot check for entry undefined: false' 6 times because you call it for 6 loops. So I think the script is now working! Now the question is: how do I define the entries to test? Should an array be built? Kind of what I was going for. I need this code to validate each period entry of 'TimeIn' and 'TimeOut' for the respective day. If I use it to validate every time entry, I think that will not work. – Bobby Jones Jul 05 '18 at 22:42
  • That should be `entry.id`, not `this.id`. – Mike 'Pomax' Kamermans Jul 05 '18 at 22:45
  • What is also throwing me off is your original function `Entry` calls for four variables: `(pdfObject, day, pd, dateOffset)` but the sample only uses three? – Bobby Jones Jul 05 '18 at 22:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/174456/discussion-between-bobby-jones-and-mike-pomax-kamermans). – Bobby Jones Jul 05 '18 at 23:00