Tuesday, 14 March 2017

Scratching an itch with JSFiddle

I'm presently moving the Witchford Archers site away from the LAMP stack towards a serverless architecture on AWS and so I needed to move the attendance records from MySQL to DynamoDB. Presently they look like this:

CREATE TABLE IF NOT EXISTS `attendance` (
  `date` date NOT NULL,
  `username` varchar(255) NOT NULL DEFAULT '0',
  `name` varchar(255) DEFAULT NULL,
  `dues` tinyint(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `attendance`
--

INSERT INTO `attendance` (`date`, `username`, `name`, `dues`) VALUES
('2015-01-07', 'Others', '8', NULL),
('2015-01-14', 'Others', '10', NULL),
('2015-01-21', 'Others', '9', NULL),
('2015-01-28', 'Others', '8', NULL),
('2015-02-04', 'Others', '8', NULL),
('2015-12-09', 'Others', '9', NULL),
('2015-12-16', 'Others', '9', NULL),
('2015-12-23', 'Others', '8', NULL),
('2015-12-30', 'Others', '7', NULL),
('2016-01-13', 'user1@example.com', 'Dominic', 1),
('2016-01-13', 'user2@example.com', 'Derek', 1),
('2016-01-13', 'user3@example.com', 'Gemma', 1),
('2016-01-13', 'user5@example.com', 'Chris', 1),
('2016-01-13', 'user6+user2@example.com', 'Jack', 1),
('2016-01-13', 'user4@example.com', 'James', 1),
('2016-01-13', 'user7@example.com', 'Peter', 1),
('2016-01-13', 'user8@example.com', 'Mariusz', 1),
('2016-01-13', 'user9@example.com', 'Patrick', 1),
('2016-01-13', 'user11@example.com', 'Russell', 1),
('2016-01-13', 'user10@example.com', 'Sarah', 1),
('2016-01-17', 'user1@example.com', 'Dominic', 1),
('2016-01-17', 'user2@example.com', 'Derek', 0),
('2016-01-17', 'user3@example.com', 'Gemma', 0),
('2016-01-17', 'user5@example.com', 'Chris', 0),
('2016-01-17', 'user6+user2@example.com', 'Jack', 0),
('2016-01-17', 'user7@example.com', 'Peter', 0),
('2016-01-19', 'user1@example.com', 'Dominic', 0),
('2016-01-19', 'user2@example.com', 'Derek', 0),
('2016-01-19', 'user3@example.com', 'Gemma', 0),
('2016-01-19', 'user4@example.com', 'James', 0);

Using a Text Editor and a few Macros I converted this into a CSV file:

date,username,name,dues
2015-01-07,Others,8,NULL
2015-01-14,Others,10,NULL
2015-01-21,Others,9,NULL
2015-01-28,Others,8,NULL
2015-02-04,Others,8,NULL
2015-12-09,Others,9,NULL
2015-12-16,Others,9,NULL
2015-12-23,Others,8,NULL
2015-12-30,Others,7,NULL
2016-01-13,user1@example.com,Dominic,1
2016-01-13,user2@example.com,Derek,1
2016-01-13,user3@example.com,Gemma,1
2016-01-13,user5@example.com,Chris,1
2016-01-13,user6+user2@example.com,Jack,1
2016-01-13,user4@example.com,James,1
2016-01-13,user7@example.com,Peter,1
2016-01-13,user8@example.com,Mariusz,1
2016-01-13,user9@example.com,Patrick,1
2016-01-13,user11@example.com,Russell,1
2016-01-13,user10@example.com,Sarah,1
2016-01-17,user1@example.com,Dominic,1
2016-01-17,user2@example.com,Derek,0
2016-01-17,user3@example.com,Gemma,0
2016-01-17,user5@example.com,Chris,0
2016-01-17,user6+user2@example.com,Jack,0
2016-01-17,user7@example.com,Peter,0
2016-01-19,user1@example.com,Dominic,0
2016-01-19,user2@example.com,Derek,0
2016-01-19,user3@example.com,Gemma,0
2016-01-19,user4@example.com,James,0

With this I used Papa Parse and a few other scripts/plugins to create the data I needed for DynamoDB. I guess the main issue I wanted to raise was the use of JSFiddle. This is the final result:

[
    {
        "Date": "2015-01-07",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-14",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-21",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-28",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-02-04",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-09",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-16",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-23",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-30",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2016-01-13",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Chris",
                "User": "user5@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Jack",
                "User": "user6+user2@example.com",
                "Status": "Paid"
            },
            {
                "Name": "James",
                "User": "user4@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Peter",
                "User": "user7@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Mariusz",
                "User": "user8@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Patrick",
                "User": "user9@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Russell",
                "User": "user11@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Sarah",
                "User": "user10@example.com",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2016-01-17",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Chris",
                "User": "user5@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Jack",
                "User": "user6+user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Peter",
                "User": "user7@example.com",
                "Status": "Unpaid"
            }
        ]
    },
    {
        "Date": "2016-01-19",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "James",
                "User": "user4@example.com",
                "Status": "Unpaid"
            }
        ]
    }
]

JSFiddle was created by Piotr Zalewa and it is described by him thus:

JsFiddle is a playground for web developers, a tool which may be used in many ways. One can use it as an online editor for snippets build from HTML, CSS and JavaScript.

The code can then be shared with others, embedded on a blog, etc. Using this approach, JavaScript developers can very easily isolate bugs. We aim to support all actively developed frameworks - it helps with testing compatibility.

I love it! I use it ever-so-much for all sorts of things, not least because it's a quick way of getting a proof of concept up and running as quickly as possible. I use all sorts of IDEs and Text Editor in my work but JSFiddle, as well as Chrome's developer console, is where I do most of my work.

Anyway, the rather over-engineered code which produced the JSON above is this:

let syntaxHighlight = (json) => json
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) => {
        var cls = 'number';
        if (/^"/.test(match)) {
            if (/:$/.test(match)) {
                cls = 'key';
            } else {
                cls = 'string';
            }
        } else if (/true|false/.test(match)) {
            cls = 'boolean';
        } else if (/null/.test(match)) {
            cls = 'null';
        }
        return '<span class="' + cls + '">' + match + '</span>';
    });

$("form").on("click", ".fileinput-upload", (e) => {
    e.preventDefault();
    if (window.File && window.FileReader && window.FileList && window.Blob) {
        var files = document.getElementById("input").files;
        if (files[0].type === "application/vnd.ms-excel") {
            data = [];
            Papa.parse(files[0], {
                "download": true,
                "header": true,
                "dynamicTyping": true,
                "complete": results => {
                    let data = [];
                    let Status = ["Unpaid", "Paid", "Subscriber"];
                    results.data.forEach((element) => {
                        let index = data.findIndex(e => e.Date === element.date);
                        if (index === -1) {
                            if (element.username === "Others") {
                                let Attendance = [];
                                for (let i = 0; i < element.name; i++) {
                                    Attendance.push({
                                        "Name": "Unknown",
                                        "User": "Unknown",
                                        "Status": Status[1]
                                    });
                                }
                                data.push({
                                    "Date": element.date,
                                    "Attendance": Attendance
                                });
                            } else {
                                data.push({
                                    "Date": element.date,
                                    "Attendance": [{
                                        "Name": element.name,
                                        "User": element.username,
                                        "Status": Status[element.dues]
                                    }]
                                });
                            }
                        } else {
                            if (element.username === "Others") {
                                for (let i = 0; i < element.name; i++) {
                                    data[index].Attendance.push({
                                        "Name": "Unknown",
                                        "User": "Unknown",
                                        "Status": Status[1]
                                    });
                                }
                            } else {
                                data[index].Attendance.push({
                                    "Name": element.name,
                                    "User": element.username,
                                    "Status": Status[element.dues]
                                });
                            }
                        }
                    });
                    $("#output").html(syntaxHighlight(JSON.stringify(data, undefined, 4))).show();
                }
            });
        } else {
            toastr["error"]("Please only upload CSV format files.", "Naughty!");
        }
    } else {
        toastr["warning"]("The File APIs are not fully supported in this browser.", "Oops!");
    }
});

I'm using jQuery, BootStrap, Toastr, Papa Parse, Bootstrap File Input and I borrowed user123444555621's answer on Stack Overflow. Many of the resources were grabbed from cdnjs.

But I guess the main thing is that I managed to get all this done really quickly and relatively painlessly thanks to the wealth of resources out there, often available for free! I can spin up a simple application to process data just in my browser rather than setting up a server and pulling in external resources, pulling in Frameworks and libraries as I like. This isn't without some problems though and I guess the skill lies in knowing which things work well with others.

Tuesday, 28 February 2017

Roll your own pagination with Bootstrap and MDBootstrap

My previous post about adding MDBootstrap styling to DataTables got me thinking about pagination (that and reading about jQuery Pagination Plugins). As such, I decided to roll my own dynamic pagination. While I got around to it I got to thinking about the bits between the arrows, the filling if you will. I really liked the start/previous mechanism as well as the next/last one. So that within the two ends there's a set of numbers. That's generally fine with a limited set of numbers to page though but what if you've got more than 7, for instance?

I got to thinking about ellipses and how they might do in order to indicate the existence of further numbers past a given set (in my example I'm limiting those to 3). So at the beginning of your paging experience, you can see the first three (with them becoming active as you page through), once you pass a given threshold though you see that there are more behind you than you can see and that you are unable to reach them without going back.

The same mechanism occurs towards the end of your paging experience. I quite like this approach and I've worked up a simple JSFiddle to illustrate the action:

This is the relevant function:

let buildPagination = function(target, start, hits) {
    let pages = Math.ceil(hits / 10);
    let pagesArray = [];
    /*
     * Sort out pages links
     */
    if (pages < 5) {
        for (let i = 0; i < pages; i++) {
            let pageBubble = $("<li></li>", {
                "data-start": i * 10
            });
            (Math.floor((i) * 10) === start) && pageBubble.addClass("active");
            pageBubble.append($("<a></a>", {
                "href": "#",
                "text": i + 1
            }));
            pagesArray.push(pageBubble)
        }
    } else {
        /*
         * Show three pages, and disabled padding if required
         */
        if (start === 0) {
            for (let i = 0; i < 3; i++) {
                let pageBubble = $("<li></li>", {
                    "data-start": i * 10
                });
                (Math.floor((i) * 10) === start) && pageBubble.addClass("active");
                pageBubble.append($("<a></a>", {
                    "href": "#",
                    "text": i + 1
                }));
                pagesArray.push(pageBubble);
            }
            let pageBubble = $("<li></li>", {
                "class": "disabled"
            });
            pageBubble.append($("<a></a>", {
                "href": "#",
                "text": ""
            }));
            pagesArray.push(pageBubble);
        } else if ((start + 10) >= hits) {
            let pageBubble = $("<li></li>", {
                "class": "disabled"
            });
            pageBubble.append($("<a></a>", {
                "href": "#",
                "text": ""
            }));
            pagesArray.push(pageBubble);
            for (let i = pages - 3; i < pages; i++) {
                let pageBubble = $("<li></li>", {
                    "data-start": i * 10
                });
                (Math.floor((i) * 10) === start) && pageBubble.addClass("active");
                pageBubble.append($("<a></a>", {
                    "href": "#",
                    "text": i + 1
                }));
                pagesArray.push(pageBubble)
            }
        } else {
            let pageBubble = $("<li></li>", {
                "class": "disabled"
            });
            pageBubble.append($("<a></a>", {
                "href": "#",
                "text": ""
            }));
            pagesArray.push(pageBubble);
            let current = Math.floor(start / 10);
            for (let i = current - 1; i < current + 2; i++) {
                let pageBubble = $("<li></li>", {
                    "data-start": i * 10
                });
                (i === current) && pageBubble.addClass("active");
                pageBubble.append($("<a></a>", {
                    "href": "#",
                    "text": i + 1
                }));
                pagesArray.push(pageBubble)
            }
            pageBubble = $("<li></li>", {
                "class": "disabled"
            });
            pageBubble.append($("<a></a>", {
                "href": "#",
                "text": ""
            }));
            pagesArray.push(pageBubble);
        }
    }
    /*
     * Left links (Start and previous)
     */
    let first_page = $("<li></li>", {
        "data-start": 0
    });
    (start === 0) && first_page.addClass("disabled");
    first_page.append($("<a></a>", {
        "href": "#!"
    }).append($("<i></i>", {
        "class": "material-icons",
        "text": "first_page"
    })));
    let chevron_left = $("<li></li>", {
        "data-start": ~~start - 10
    });
    (start === 0) && chevron_left.addClass("disabled");
    chevron_left.append($("<a></a>", {
        "href": "#!"
    }).append($("<i></i>", {
        "class": "material-icons",
        "text": "chevron_left"
    })));
    /*
     * Right link (Next and last)
     */
    let chevron_right = $("<li></li>", {
        "data-start": start + 10
    });
    ((start + 10) >= hits) && chevron_right.addClass("disabled");
    chevron_right.append($("<a></a>", {
        "href": "#!"
    }).append($("<i></i>", {
        "class": "material-icons",
        "text": "chevron_right"
    })));
    let last_page = $("<li></li>", {
        "data-start": Math.floor(hits / 10) * 10
    });
    ((start + 10) >= hits) && last_page.addClass("disabled");
    last_page.append($("<a></a>", {
        "href": "#!"
    }).append($("<i></i>", {
        "class": "material-icons",
        "text": "last_page"
    })));
    /*
     * Add out links
     */
    target.empty();
    target.append(first_page);
    target.append(chevron_left);
    target.append(pagesArray);
    target.append(chevron_right);
    target.append(last_page);
};

This did take me far too long to figure out though as the Maths was complicated (at least it was for me), and I'm still not overly sure that it's correct so if anyone want to take a look please do and tell me where I've gone wrong.

Thursday, 16 February 2017

MDB 3 DataTable Style

It's taken ages and ages but I've finally gotten around to creating a theme for DataTables which uses the lovely inputs from Material Design for Bootstrap (MDB). I've been playing with MDB for a while now and love its look and feel. I've loved DataTables for as long as I can remember so getting them to play nicely with each other was on my radar for a while. I finished that work last night and published it to GitHub this morning and I'm really, quite pleased with the results. This is the Readme:

DataTables comes with few themes, including those for Bootstrap 3 and 4. These themes work really well for Material Design for Bootstrap (MDB) as is, but...

This work enhances the Bootstrap 3 theme and takes advantage of the nice pagination and inputs available in MDB to make it look even better!

Initially only the pagination was implemented but, with some help and advice from Allan I got the filter input as well as the length select to work, using Input Fields and Material Select respectively.

There is presently no support for custom renderers for these elements so the "initComplete" function has been hijacked. Once custom renderers are implemented I'll move the work over to a that mechanism.

Once I have more time I'll also adapt it so that it will work with BootStrap 4 and MDB4.

And this is the code:

/*! DataTables MDBootstrap 3 integration
 * Based upon DataTables Bootstrap 3 integration
 * Dominic Myers <annoyingmouse@gmail.com>
 */

/**
 * DataTables integration for MDBootstrap 3. This requires MDBootstrap 3 and
 * DataTables 1.10 or newer.
 *
 * This file sets the defaults and adds options to DataTables to style its
 * controls using Bootstrap.
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'datatables.net'], function ($) {
            return factory($, window, document);
        });
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = function (root, $) {
            if (!root) {
                root = window;
            }
            if (!$ || !$.fn.dataTable) {
                // Require DataTables, which attaches to jQuery, including
                // jQuery if needed and have a $ property so we can access the
                // jQuery object that is used
                $ = require('datatables.net')(root, $).$;
            }
            return factory($, root, root.document);
        };
    } else {
        // Browser
        factory(jQuery, window, document);
    }
}(($, window, document, undefined) => {
    'use strict';
    let DataTable = $.fn.dataTable;
    /* Set the defaults for DataTables initialisation */
    $.extend(true, DataTable.defaults, {
        "dom": `
            <'row'
                <'col-sm-6 input-field'
                    l
                >
                <'col-sm-6 input-field'
                    f
                >
            >
            <'row'
                <'col-sm-12'
                    tr
                >
            >
            <'row'
                <'col-sm-5'
                    i
                >
                <'col-sm-7'
                    p
                >
            >`,
        "renderer": 'mdbootstrap',
        "language": {
            "lengthMenu": "_MENU_",
            "paginate": {
                "first": `
                    <i class="material-icons">
                        first_page
                    </i>`,
                "last": `
                    <i class="material-icons">
                        last_page
                    </i>`,
                "next": `
                    <i class="material-icons">
                        chevron_right
                    </i>`,
                "previous": `
                    <i class="material-icons">
                        chevron_left
                    </i>`
            }
        },
        "initComplete": () => {
            /*
             * This makes the length dropdown into a material select
             */
            let lengthDiv = $(".dataTables_length"),
                lengthSelect = lengthDiv
                    .find("select[name$='_length']"),
                lengthSelectClone = lengthSelect
                    .clone(true);
            lengthDiv
                .replaceWith(lengthSelectClone);
            lengthSelectClone
                .material_select();
            let lengthSelectCloneCaret = lengthSelectClone
                .parent()
                .find(".caret");
            lengthSelectCloneCaret
                .css("display", "block")
                .css("width", "initial")
                .css("border-top", "initial");

            /*
             * This makes the filter input work as a material input
             */
            let filterDiv = $(".dataTables_filter"),
                filterDivParent = filterDiv
                    .parent(".input-field"),
                filterInput = filterDiv
                    .find("input[type='search']"),
                filterInputClone = filterInput
                    .clone(true),
                tableId = filterDiv
                    .closest(".dataTables_wrapper")
                    .attr("id")
                    .split("_")[0],
                filterLabel = filterInput
                    .parent("label")
                    .attr("for", tableId + "_cloned_input")
                    .empty()
                    .text("Search")
                    .clone(true);
            filterInputClone.attr({
                "id": tableId + "_cloned_input",
                "type": "text"
            }).on("blur", () => {
                /*
                 * We need to ensure that the active class is removed from the input if there
                 * isn't a value to make it look pretty.
                 */
                if (!filterInputClone.val().length) {
                    filterLabel.removeClass("active");
                }
            });
            filterDivParent
                .empty()
                .append(`
                    <i class="material-icons prefix">
                        search
                    </i>`)
                .append(filterInputClone)
                .append(filterLabel)
        }
    });
    /* Default class modification */
    $.extend(DataTable.ext.classes, {
        sWrapper: "dataTables_wrapper form-inline dt-bootstrap",
        sProcessing: "dataTables_processing panel panel-default"
    });
    /* MDBootstrap paging button renderer */
    DataTable.ext.renderer.pageButton.mdbootstrap = (settings, host, idx, buttons, page, pages) => {
        let api = new DataTable.Api(settings);
        let classes = settings.oClasses;
        let lang = settings.oLanguage.oPaginate;
        let aria = settings.oLanguage.oAria.paginate || {};
        let counter = 0;
        let getDisplayClass = button => {
            let tempBtns = {
                "tempBtnDisplay": "",
                "tempBtnClass": ""
            };
            let tempBtnDisplays = {
                "ellipsis": () => {
                    tempBtns.btnDisplay = "&#x2026;";
                    tempBtns.btnClass = "disabled";
                },
                "first": () => {
                    tempBtns.btnDisplay = lang.sFirst;
                    tempBtns.btnClass = button + (page > 0 ? '' : ' disabled');
                },
                "previous": () => {
                    tempBtns.btnDisplay = lang.sPrevious;
                    tempBtns.btnClass = button + (page > 0 ? '' : ' disabled');
                },
                "next": () => {
                    tempBtns.btnDisplay = lang.sNext;
                    tempBtns.btnClass = button + (page < pages - 1 ? '' : ' disabled');
                },
                "last": () => {
                    tempBtns.btnDisplay = lang.sLast;
                    tempBtns.btnClass = button + (page < pages - 1 ? '' : ' disabled');
                },
                "default": () => {
                    tempBtns.btnDisplay = button + 1;
                    tempBtns.btnClass = page === button ? 'active' : '';
                }
            };
            (tempBtnDisplays[button] || tempBtnDisplays["default"])();
            return tempBtns;
        };
        let attach = (container, buttons) => {
            let i, ien, node, button;
            let clickHandler = e => {
                e.preventDefault();
                if (!$(e.currentTarget).hasClass('disabled') && api.page() != e.data.action) {
                    api.page(e.data.action).draw('page');
                }
            };
            for (i = 0, ien = buttons.length; i < ien; i++) {
                button = buttons[i];

                if ($.isArray(button)) {
                    attach(container, button);
                }
                else {
                    let btnDisplayClass = getDisplayClass(button);
                    if (btnDisplayClass.btnDisplay) {
                        node = $('<li>', {
                            'class': classes.sPageButton + ' ' + btnDisplayClass.btnClass,
                            'id': idx === 0 && typeof button === 'string' ?
                                settings.sTableId + '_' + button :
                                null
                        }).append($('<a>', {
                                'href': '#',
                                'aria-controls': settings.sTableId,
                                'aria-label': aria[button],
                                'data-dt-idx': counter,
                                'tabindex': settings.iTabIndex
                            }).html(btnDisplayClass.btnDisplay)
                        ).appendTo(container);
                        settings.oApi._fnBindAction(
                            node, {action: button}, clickHandler
                        );
                        counter++;
                    }
                }
            }
        };
        // IE9 throws an 'unknown error' if document.activeElement is used
        // inside an iframe or frame.
        let activeEl;
        try {
            // Because this approach is destroying and recreating the paging
            // elements, focus is lost on the select button which is bad for
            // accessibility. So we want to restore focus once the draw has
            // completed
            activeEl = $(host).find(document.activeElement).data('dt-idx');
        } catch (e) {
        }
        attach(
            $(host).empty().html('<ul class="pagination pag-circle"/>').children('ul'), buttons
        );
        if (activeEl) {
            $(host).find('[data-dt-idx=' + activeEl + ']').focus();
        }
    };
    return DataTable;
}));