define([
"jquery",
"underscore",
"backbone",
"localforage",
"clipboard",
"text!templates/draftsTemplate.html",
], function ($, _, Backbone, LocalForage, Clipboard, draftsTemplate) {
/**
* @class DraftsView
* @classdesc A view that lists the local submission drafts for this user
* @classcategory Views
* @extends Backbone.View
*/
var view = Backbone.View.extend(
/** @lends DraftsView.prototype */ {
type: "DraftsView",
el: "#Content",
className: "div",
template: _.template(draftsTemplate),
initialize: function () {
return this;
},
render: function () {
var view = this;
var drafts = [];
LocalForage.iterate(function (value, key, iterationNumber) {
// Extract each draft
drafts.push({
key: key,
value: value,
fileName:
typeof value.title === "string"
? value.title.substr(0, 50).replace(/[^a-zA-Z0-9_]/, "_")
: "draft",
friendlyTimeDiff: view.friendlyTimeDiff(value.datetime),
});
})
.then(function () {
// Sort by datetime
drafts = _.sortBy(drafts, function (draft) {
return draft.value.datetime.toString();
}).reverse();
})
.then(function () {
// Render
view.$el.html(
view.template({
drafts: drafts,
}),
);
// Insert downloadables
view.insertDownloadables();
// Insert copiables
view.insertCopiables();
})
.catch(function (err) {
console.log(err);
view.$el.html("<div>There was an error listing drafts.</div>");
});
return this;
},
/** Attach a click handler for download buttons that triggers a draft
* or all drafts to be downloaded
*/
insertDownloadables: function () {
var view = this;
// Build handlers for single downloaders
_.each(this.$el.find(".draft-download"), function (el) {
var a = $(el).find("a.download");
var text = $(el).find("textarea")[0].value;
var fileName = a.data("filename") || "draft.xml";
$(a).on("click", view.createDownloader(text, fileName));
});
// Build handler for Download All button
this.$el.find(".download-all").on("click", this.createDownloadAll());
},
/** Creates a function for use as an event handler in insertDownloadables
* that creates a closure around the content (text) and filename and
* causes the browser to download the draft when clicked
*/
createDownloader: function (text, fileName) {
return function () {
var blob = new Blob([text], { type: "application/xml" });
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none;";
a.href = url;
a.download = fileName;
a.click();
a.remove();
};
},
createDownloadAll: function () {
var drafts = [];
_.each(this.$el.find("textarea"), function (textarea) {
drafts.push(textarea.value);
});
var doc =
'<?xml version="1.0" encoding="utf-8"?>\n<drafts>\n' +
_.map(drafts, function (draft) {
return "\t<draft>\n\t\t" + draft + "\n\t</draft>\n";
}).join("") +
"</drafts>";
return function () {
var blob = new Blob([doc], { type: "application/xml" });
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none;";
a.href = url;
a.download = "drafts.xml";
a.click();
a.remove();
};
},
insertCopiables: function () {
var copiables = $(".copy-to-clipboard");
_.each(copiables, function (copiable, i) {
var clipboard = new Clipboard(copiable, {
text: function (trigger) {
return $("#draft-" + i).text();
},
});
clipboard.on("success", function (e) {
var el = $(e.trigger);
$(el).html(
$(document.createElement("span")).addClass(
"icon icon-ok success",
),
);
// Use setTimeout instead of jQuery's built-in Events system because
// it didn't look flexible enough to allow me update innerHTML in
// a chain
setTimeout(function () {
$(el).html('<i class="icon icon-copy"></i> Copy to Clipboard');
}, 500);
});
});
},
/**
* Formats a time difference, in milliseconds, in a human-friendly way
* @param {string} datetime: A datetime as a string which needs to be
* parsed before working with
*/
friendlyTimeDiff: function (datetime) {
var friendly,
now = new Date(),
then = new Date(datetime),
diff = now - then;
// Fall through from largest to smallest, finding the largest unit
// that describes the difference with a unit value of one or greater
if (diff > 2678400000) {
friendly = {
value: Math.round(diff / 2678400000),
unit: "month",
};
} else if (diff > 604800000) {
friendly = {
value: Math.round(diff / 604800000),
unit: "week",
};
} else if (diff > 86400000) {
friendly = {
value: Math.round(diff / 86400000),
unit: "day",
};
} else if (diff > 3600000) {
friendly = {
value: Math.round(diff / 3600000),
unit: "hour",
};
} else if (diff > 60000) {
friendly = {
value: Math.round(diff / 60000),
unit: "minute",
};
} else if (diff > 1000) {
friendly = {
value: Math.round(diff / 1000),
unit: "second",
};
} else {
// Shortcircuit if really small and return...
return "just now";
}
// Pluralize
if (friendly.value !== 1) {
friendly.unit = friendly.unit + "s";
}
return friendly.value + " " + friendly.unit + " ago";
},
},
);
return view;
});