Link to home
Start Free TrialLog in
Avatar of GessWurker
GessWurker

asked on

How to sort array by date substring?

In a web app for which I'm responsible, I've been asked to sort contents of a form field by date descending using the date that starts each list item. I'd like to do this either onBlur or  onSave because users are entering stuff willy-nilly. In any event, though I can very easily sort items alphabetically using javascript's array sort() method, sorting by date substring descending is whole 'nuther kettle of fish.  

Here's an example of what I'd need to sort by date descending (and yes, each item has a bullet char):

•01/21/2013 - something happened; something else happened on 01/30/2013.
•04/02/2014 - John J. buys a new car
•02/25/2014 - We went to a movie. It was awesome, but a better one comes out on 12/31/2019.
•05/21/2012 - Let's keep sorting things.


Let's say the above is all included in a form field with id=myActivities. I'd like to, onBlur, get this:

•04/02/2014 - John J. buys a new car
•02/25/2014 - We went to a movie. It was awesome, but a better one comes out on 12/31/2019.
•01/21/2013 - something happened; something else happened on 01/30/2013.
•05/21/2012 - Let's keep sorting things.

Any brilliant sorters out there? I'm at your mercy.

By the way, in a windows app I can use to edit/update existing content, I use the (slightly proprietary looking) jscript code below. It works properly, but it's simple alphabetical sorting. Nothing to do with substring dates.

****************************************

var sortBox = new Array("Events");

function onRecordSave()
{
var i;
var es = Application.entrySeparator;
var fieldBox; // box object
var fieldString; // contents of the box
var entryArray; // array of field entries

// This loop will sort the entries in each box listed
// above, individually.
for (i = 0; i < sortBox.length; i++)
{
fieldBox = Form.boxes(sortBox[i]);
if (fieldBox) // ensure that the box exists
{
fieldString = fieldBox.content;
entryArray = fieldString.split(es);

// sort the entries using the JScript sort method
entryArray = entryArray.sort();
fieldString = entryArray.join(es);
fieldBox.content = fieldString;
}
}
}
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

My personal opinion is that you're not sorting correctly if you want to sort based on a substring. You CAN, but it would be highly unreliable.

My recommendation would be to parse the content into an array of objects, where each object has a date property (which holds a Date object that is built from the MM/DD/YYYY string value) and an event property (to hold the text part of each record/line).

Then simply sort on the date property, and Javascript will handle the detection of which date is greater than the other. Example:

<script>
// Start with one big block of unsorted text
var str = "•01/21/2013 - something happened; something else happened on 01/30/2013.\n" + 
"•08/17/2019 - Today\n" + 
"•04/02/2014 - John J. buys a new car\n" + 
"•02/25/2014 - We went to a movie. It was awesome, but a better one comes out on 12/31/2019.\n" + 
"•05/21/2012 - Let's keep sorting things. ";

// Parse into an array of objects
listOfEvents = [];
var matchResult;
var re = new RegExp("^.([0-9]{2}/[0-9]{2}/[0-9]{4}) - ([^•]+)", "gms"); // Regex to match an entry

// Keep looping through all matches
while(matchResult = re.exec(str))
{
  // Build a new object with a "date" and an "event" property
  listOfEvents.push({ date: new Date(matchResult[1]), event: matchResult[2] });
}

// Sort the array by the date property in descending order
listOfEvents.sort((a, b) => (a.date > b.date) ? -1 : 1) // -1 : 1 = Descending, 1 : -1 = Ascending

// Tada!
console.log(listOfEvents);
</script>

Open in new window


From there, you could loop through the sorted list and reconstruct it back as one big text block if you wanted to.
function onRecordSave() {
    var es = Application.entrySeparator;
    var rgx =  /\d\d\/\d\d\/\d\d/;
    var myActivities = document.getElementById("myActivities").value.split(es);
    myActivities.sort(function(box1, box2) {
        var date1 = box1.match(rgx)[0];
        var date2 = box2.match(rgx)[0];
        return date2 - date1;
    });
    document.getElementById("myActivities").value = myActivities.join(es);
}

Open in new window

Avatar of GessWurker
GessWurker

ASKER

Hi, gr8Gonzo - So far, no luck with your example, but perhaps it's a mistake on my part. When I launch a page with your script and hit F12, I can see the nice array, but I then see 6 instances of this: "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist." No listOfEvents displays.
Hi leakim971 -

You suggestion uses code (i.e., Application.entrySeparator) that works in the windows workstation application but not the web. That being the case, your code can't (and I tested, it) doesn't work as-is. I tried modifying it, but to no avail, so far. Here's what I tried:

var srcBox = Form.boxes("CaseEvents"); //the form box we're working with
var srcTxt = srcBox.content  //the content we're working with

function onRecordSave() {
    var es = Application.entrySeparator;
    var rgx =  /\d\d\/\d\d\/\d\d/;
    var myActivities = srcTxt.split(es);
    myActivities.sort(function(box1, box2) {
        var date1 = box1.match(rgx)[0];
        var date2 = box2.match(rgx)[0];
        return date2 - date1;
    });
    myActivities = myActivities.join(es);
}


Unfortunately, the windows app provides zero in the way debugging utilities (it's a legacy app), so... things either work or they don't, and no helpful errors are reported.

Just for informational purposes, here's a sample script from the legacy app that shows why I went with "content" rather than "value" in your original submission:

Sample JavaScript Script 5: Copy first word of one field to another field (if the second field is empty) when the record saved
 

function onRecordSave()
{
var srcBox = Form.boxes("name1");
if (srcBox) // be sure there is a box with the given name
{
var srcText = srcBox.content;
if (srcText != "") // proceed only if the first box is nonempty
{
var tgtBox = Form.boxes("name2");
if (tgtBox && tgtBox.content == "") // second box must exist and be empty
{
var allWords = srcText.split(" "); // allWords is an array of words
tgtBox.content = allWords[0];
}
}
}
}
That error doesn't come from the code I provided. Usually when you see that specific error, you have some chrome extension installed that is having problems.

If you see that array output, then that indicates the code snippet I provided has ended successfully. What you see is the sorted array. Anything after that is unrelated.

By the way, I intentionally kept the final output as an array since it gives you a lot more flexibility in terms of what you can do from there.
Ah... I got distracted by the "errors".    : )

So yes, I see the desired sort:

0: {date: Sat Aug 17 2019 00:00:00 GMT-0400 (Eastern Daylight Time), event: "Today↵"}
1: {date: Wed Apr 02 2014 00:00:00 GMT-0400 (Eastern Daylight Time), event: "John J. buys a new car↵"}
2: {date: Tue Feb 25 2014 00:00:00 GMT-0500 (Eastern Standard Time), event: "We went to a movie. It was awesome, but a better one comes out on 12/31/2019.↵"}
3: {date: Mon Jan 21 2013 00:00:00 GMT-0500 (Eastern Standard Time), event: "something happened; something else happened on 01/30/2013.↵"}
4: {date: Mon May 21 2012 00:00:00 GMT-0400 (Eastern Daylight Time), event: "Let's keep sorting things. "}

Now I need to cobble things back together to match the original formatting. I'll endeavor to  figure that out.
Coming up empty. Can you put things back together so that by the end of the script, the just-sorted list is displayed OR ready to replace the original unordered list?

•04/02/2014 - John J. buys a new car
•02/25/2014 - We went to a movie. It was awesome, but a better one comes out on 12/31/2019.
•01/21/2013 - something happened; something else happened on 01/30/2013.
•05/21/2012 - Let's keep sorting things.
Using your original code :
var sortBox = new Array("Events");

function onRecordSave()
{
    var i;
    var es = Application.entrySeparator;
    var fieldBox; // box object
    var fieldString; // contents of the box
    var entryArray; // array of field entries
    var rgx =  /\d\d\/\d\d\/\d\d/;

// This loop will sort the entries in each box listed
// above, individually.
    for (i = 0; i < sortBox.length; i++)
    {
        fieldBox = Form.boxes(sortBox[i]);
        if (fieldBox) // ensure that the box exists
        {
            fieldString = fieldBox.content;
            entryArray = fieldString.split(es);

// sort the entries using the JScript sort method
            entryArray = entryArray.sort(function(box1, box2) {
                var date1 = box1.match(rgx)[0];
                var date2 = box2.match(rgx)[0];
                return date1 - date2;
            });
            fieldString = entryArray.join(es);
            fieldBox.content = fieldString;
        }
    }
}

Open in new window

OK. I inserted "alerts" using the application lingo to see what's going on. I found that each alert was identical. The date sort function is having no effect.  Application.message(entryArray) is exactly the same as Application.message(fieldBox.content). I've attached the code I tested with.
CodePlusAlerts.txt
and how look the alert (not the code)?
var sortBox = new Array("Events");

function onRecordSave()
{
    var i;
    var es = Application.entrySeparator;
    var fieldBox; // box object
    var fieldString; // contents of the box
    var entryArray; // array of field entries
    var rgx =  /\d\d\/\d\d\/\d\d/;

// This loop will sort the entries in each box listed
// above, individually.
    for (i = 0; i < sortBox.length; i++)
    {
        fieldBox = Form.boxes(sortBox[i]);
        if (fieldBox) // ensure that the box exists
        {
            fieldString = fieldBox.content;
            entryArray = fieldString.split(es);
            Application.message(typeof entryArray + "\n" + entryArray); //could you do a screenshot ?

// sort the entries using the JScript sort method
            entryArray.sort(function(box1, box2) {
                var date1 = box1.match(rgx)[0];
                var date2 = box2.match(rgx)[0];
                return date1 - date2;
            });
            Application.message(typeof entryArray + "\n" + entryArray); //no change. Same as Application.message(entryArray) above.
            fieldString = entryArray.join(es);
            fieldBox.content = fieldString;
            Application.message(fieldBox.content);  //no change. Same as both Application.message(entryArray) lines above.
        }
    }
}

Open in new window

See attached.
User generated image
replace :
                var date1 = box1.match(rgx)[0];
                var date2 = box2.match(rgx)[0];
by :
                var date1 = new Date(box1.match(rgx)[0]);
                var date2 = new Date(box2.match(rgx)[0]);
No sorting is happening. I'm showing the original contents of the field and the last Application.message:
User generated image
sorting is happening but in the wrong order

replace :
return date1 - date2;
by :
return date2 - date1;
Well... not quite. Elements are getting moved around, but I'm looking for descending sort, with most recent at top. That's not happening. In fact, I don't see any particular sort logic.

User generated image
your last message the elements are sorted from most recent to less recent right?
your previous message it was less recent to most recent right?

why do you have now different from you initial post? it was good to have unordered element instead ordered
maybe I miss something :( ?
Yes... from the beginning of my post, I wanted to sort the items by date most recent (at top) to oldest. That's not what's happening. I just restored your code to: return date1 - date2;  Then, I performed the Save/Sort.

Note how the list was sorted. As you can see, we start with a 2012 date, followed by a 2014 date, followed by a 2012 date, followed by 2013. But what I want is newest (at top) to oldest (at bottom). Maybe I am missing something?

•03/22/2012 - requests for information were requested.
•04/21/2014 - Dismissal and Notice of Rights issued by EEOC
•04/30/2012 - responses to requests for information were submitted
•05/09/2013 - additional requests for information was requested
•05/29/2013 - response to requests fro information were submitted
•07/05/2011 position statement submitted
do you still have this too ?
                var date1 = new Date(box1.match(rgx)[0]);
alert("date1:" + date1); // let's see if you get the right date
                var date2 = new Date(box2.match(rgx)[0]);
alert("date2:" + date2); // let's see if you get the right date

instead just :
                var date1 = box1.match(rgx)[0];
                var date2 = box2.match(rgx)[0];
SOLUTION
Avatar of leakim971
leakim971
Flag of Guadeloupe image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
See attached pdf (and this was pretty painful to create!)  : )ApplicationMessages.pdf
crossed message? did you get my previous message?
Still not sorting. User generated image I used this code:
var sortBox = new Array("Events");

function onRecordSave()
{
    var i;
    var es = Application.entrySeparator;
    var fieldBox; // box object
    var fieldString; // contents of the box
    var entryArray; // array of field entries
    var rgx =  /\d\d\/\d\d\/\d\d\d\d/;

// This loop will sort the entries in each box listed
// above, individually.
    for (i = 0; i < sortBox.length; i++)
    {
        fieldBox = Form.boxes(sortBox[i]);
        if (fieldBox) // ensure that the box exists
        {
            fieldString = fieldBox.content;
            entryArray = fieldString.split(es);
            Application.message(entryArray);

// sort the entries using the JScript sort method
            entryArray.sort(function(box1, box2) {
                var date1 = new Date(box1.match(rgx)[0]);
                var date2 = new Date(box2.match(rgx)[0]);
                return date2 - date2;
            });
            Application.message(entryArray); //no change. Same as Application.message(entryArray) above.
            fieldString = entryArray.join(es);
            fieldBox.content = fieldString;
            Application.message(fieldBox.content);  //no change. Same as both Application.message(entryArray) lines above.
        }
    }
}

Open in new window

ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Looks like it's working!! I will test a bit further and will get back to you tomorrow. But it's looking good!!!
Note that the overall approach here is the same as what I initially suggested - regex to extract the date and parse it to a Date object, then sort based on the Date object.

The primary difference is that my approach should handle events that have multiple lines of text and the data is parsed into nice, organized objects.

I would strongly recommend becoming familiar with how to work with JavaScript objects. It will help you greatly in data-driven apps that utilize JavaScript.
@leakim971 -

gr8gonzo makes a good point. I've been testing things, and I've found that the presence of an extra return or an orphan bullet breaks the script. How can we modify the script so that it doesn't choke if it runs into the possibilities below?

Possible scenario 1:


•04/04/2013 - dismissal and notice of right to sue issued
•06/16/2014 no change in status
•02/12/2013 - position statement submitted to EEOC

Possible scenario 2:


•04/04/2013 - dismissal and notice of right to sue issued

•06/16/2014 no change in status
•02/12/2013 - position statement submitted to EEOC


I tested both of the scenarios above, and the script could not process them. Can the regx be fine-tuned to handle these scenarios? Or... is there a way to strip out empty bullets and/or lines and then process?
BTW: If I should ask a new question to resolve this, let me know. I understand my original question did not include these new scenarios.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Reformatting dates isn't possible.  Edge cases to cover (i.e., strip) would be:

1) lines with orphan bullet + line break and
2) lines comprised of nothing but an orphan line break ( I think: \r\n )
So does the above example/output match what you need?
if you want my point. the hard part for your question was to understand the context you're running the code.
to resolve your issue you just need the good Application.separator which is a Regular Expression ans use it to split. the code doesn't change only this separator.
Sorry. Got stuck in the weeds. Yes... the output looks like it would do the trick. I'll need to work it into the proprietary data management system I've got. I'll report back as soon as I can. Note: Your code looks good for the web-accessible portion, while leakim971's code is specific to the windows workstation portion (though the concepts are, as we've discussed, essentially the same).
Folks: I used each of your solutions. One was most easily implemented in the windows workstation desktop application situation; the other was appropriate for the web version. Yes... it could have been possible to rewrite either solution so there were two versions, one for the web, one for the windows app, but that's not how the story developed, so...

I hope I've managed to split points appropriately. Years ago, it was much easier to allocate points in Experts Exchange. Now, it seems like whatever happens will happen when I click multiple posts as being helpful.
Glad to hear it. FYI, there should be a way where you can select multiple comments as the answer. The "this is helpful" button doesn't accept a comment as a solution (it's usually there so anyone can mark a comment as helpful at any time without closing the question).
Ack... then I haven't succeeded in splitting points yet. Like I said, it used to be so easy.

Will need to research further.
Thanks again, folks! Got everything working quite nicely because of your help!
You welcome!
@gr8Gonzo: Sadly, some folks continue with old IE, and it is complaining about the line below:

listOfEvents.sort((a, b) => (a.date > b.date) ? -1 : 1)

Open in new window


IE doesn't like this:    =>  

I just confirmed that in contrast to Chrome and Edge, IE doesn't support the arrow function.
See: https://stackoverflow.com/questions/38595690/ie-11-script1002-array-filterx-arrow-functions

Do you have an alternative?
listOfEvents.sort((a, b) =>
Same as
listOfEvents.sort(function(a, b) {

You need to close } before the parenthese :

})

Check my code, you should see
I tried this:

Original: listOfEvents.sort((a, b) => (a.date > b.date) ? -1 : 1)

New:       listOfEvents.sort(function(a, b) { (a.date > b.date) ? -1 : 1 })

Doesn't work; the items are no longer sorted, but Chrome doesn't complain. No errors are reported. What have I got wrong?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
phew!
Ack. Not out of the woods yet. See what IE says:
User generated image
Oh.. but that's my bad, I think. Hang on...
Nope. IE doesn't like the original. The below doesn't work. I had changed it because I needed a different bullet char, but that isn't the issue apparently

This is rejected:
var re = new RegExp("^.([0-9]{2}/[0-9]{2}/[0-9]{4})([^•]+)", "gms");

Open in new window


I didn't think to test everything in IE 'til today. Sorry.
IE apparently doesn't like the "s" flag, but since we're not doing a .+ match but instead matching all characters that aren't the bullet, it should be fine without that flag.
Perfect. All better now. Works beautifully without the "s" flag.