Skip to content

Commit

Permalink
fix(templating): correctly handle tables
Browse files Browse the repository at this point in the history
When adding new rows to a table, the existing mechanism of storing the HTML of
a row in variable breaks at least on firefox: When parsing this HTML fragment
and creating DOM elements, the browser will ignore tags it does not expect
without proper parent tags. Appending this modified DOM branch to the table
results in broken DOM structure. Cloning the DOM branch of a row and appending
this clone to the table works just fine.

Fixes FineUploader#1246
  • Loading branch information
fjl5 committed Aug 17, 2017
1 parent 485376e commit 29ae7d8
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 8 deletions.
16 changes: 8 additions & 8 deletions client/js/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ qq.Templating = function(spec) {
log,
isEditElementsExist,
isRetryElementExist,
templateHtml,
templateDom,
container,
fileList,
showThumbnails,
Expand Down Expand Up @@ -341,7 +341,7 @@ qq.Templating = function(spec) {
scriptHtml,
fileListNode,
tempTemplateEl,
fileListHtml,
fileListEl,
defaultButton,
dropArea,
thumbnail,
Expand Down Expand Up @@ -452,7 +452,7 @@ qq.Templating = function(spec) {
throw new Error("Could not find the file list container in the template!");
}

fileListHtml = fileListNode.innerHTML;
fileListEl = fileListNode.children[0].cloneNode(true);
fileListNode.innerHTML = "";

// We must call `createElement` in IE8 in order to target and hide any <dialog> via CSS
Expand All @@ -463,8 +463,8 @@ qq.Templating = function(spec) {
log("Template parsing complete");

return {
template: qq.trimStr(tempTemplateEl.innerHTML),
fileTemplate: qq.trimStr(fileListHtml)
template: tempTemplateEl,
fileTemplate: fileListEl
};
},

Expand Down Expand Up @@ -625,7 +625,7 @@ qq.Templating = function(spec) {

container = options.containerEl;
showThumbnails = options.imageGenerator !== undefined;
templateHtml = parseAndGetTemplate();
templateDom = parseAndGetTemplate();

cacheThumbnailPlaceholders();

Expand All @@ -635,7 +635,7 @@ qq.Templating = function(spec) {

generatedThumbnails = 0;

container.innerHTML = templateHtml.template;
container.appendChild(templateDom.template.cloneNode(true));
hide(getDropProcessing());
this.hideTotalProgress();
fileList = options.fileContainerEl || getTemplateEl(container, selectorClasses.list);
Expand All @@ -662,7 +662,7 @@ qq.Templating = function(spec) {
},

addFile: function(id, name, prependInfo, hideForever, batch) {
var fileEl = qq.toElement(templateHtml.fileTemplate),
var fileEl = templateDom.fileTemplate.cloneNode(true),
fileNameEl = getTemplateEl(fileEl, selectorClasses.file),
uploaderEl = getTemplateEl(container, selectorClasses.uploader),
fileContainer = batch ? fileBatch.content : fileList,
Expand Down
136 changes: 136 additions & 0 deletions test/unit/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,54 @@ describe("templating.js", function() {
'</div>' +
'</li>' +
'</ul>' +
'</div>',
tableTemplate = '<div class="qq-uploader-selector qq-uploader">' +
'<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">' +
'<div class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>' +
'</div>' +
'<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>' +
'<span>Drop files here to upload</span>' +
'</div>' +
'<div class="qq-upload-button-selector qq-upload-button">' +
'<div>Upload a file</div>' +
'</div>' +
'<span class="qq-drop-processing-selector qq-drop-processing">' +
'<span>Processing dropped files...</span>' +
'<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>' +
'</span>' +
'<table>' +
'<thead>' +
'<tr>' +
'<th>File</th>' +
'<th>Actions</th>' +
'</tr>' +
'</thead>' +
'<tbody class="qq-upload-list-selector">' +
'<tr>' +
'<td>' +
'<div class="qq-progress-bar-container-selector">' +
'<div class="qq-progress-bar-selector qq-progress-bar"></div>' +
'</div>' +
'<span class="qq-upload-spinner-selector qq-upload-spinner"></span>' +
'<span class="qq-edit-filename-icon-selector qq-edit-filename-icon"></span>' +
'<span class="qq-upload-file-selector qq-upload-file"></span>' +
'<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">' +
'<span class="qq-upload-size-selector qq-upload-size"></span>' +
'</td>' +
'<td>' +
'<a class="qq-upload-cancel-selector qq-upload-cancel" href="#">Cancel</a>' +
'<a class="qq-upload-retry-selector qq-upload-retry" href="#">Retry</a>' +
'<a class="qq-upload-delete-selector qq-upload-delete" href="#">Delete</a>' +
'<a class="qq-upload-pause-selector" href="#">Pause</a>' +
'<a class="qq-upload-continue-selector" href="#">Continue</a>' +
'<span class="qq-upload-status-text-selector qq-upload-status-text"></span>' +
'</td>' +
'</tr>' +
'</tbody>' +
'</table>' +
'</div>';


function renderTemplate(content) {
$template = $('<script id="qq-template" type="text/template"></script>');
$template[0].text = content;
Expand Down Expand Up @@ -353,4 +399,94 @@ describe("templating.js", function() {
});
});
}

describe("test with table template", function() {
var fileContainer0;

beforeEach(function() {
renderTemplate(tableTemplate);
templating.addFile(0, "foobar");
fileContainer0 = templating.getFileContainer(0);
});

afterEach(function() {
templating.clearFiles();
});


it("adds & removes file entries", function() {
/* jshint eqnull:true */
assert.ok(templating.getFileContainer(0) != null);
templating.removeFile(0);
assert.ok(templating.getFileContainer(0) == null);
templating.addFile(0, "test");
templating.clearFiles();
assert.ok(templating.getFileContainer(0) == null);
});

it("embeds the file ID correctly", function() {
assert.ok(templating.getFileId(fileContainer0) === 0);
});

it("hides and shows spinner", function() {
templating.hideSpinner(0);
assert.ok($(fileContainer0).find(".qq-upload-spinner-selector").hasClass(HIDE_CSS));
assert.ok(!$(fileContainer0).hasClass("qq-in-progress"));

templating.showSpinner(0);
assert.ok(!$(fileContainer0).find(".qq-upload-spinner-selector").hasClass(HIDE_CSS));
assert.ok($(fileContainer0).hasClass("qq-in-progress"));
});

it("updates status text", function() {
templating.setStatusText(0, "foobar");
assert.equal($(fileContainer0).find(".qq-upload-status-text-selector").text(), "foobar");
});

it("updates file name", function() {
templating.updateFilename(0, "123abc");
assert.equal($(fileContainer0).find(".qq-upload-file-selector").text(), "123abc");
});

it("updates size text", function() {
templating.updateSize(0, "123MB");
assert.equal($(fileContainer0).find(".qq-upload-size-selector").text(), "123MB");
});

it("hides and shows delete link", function() {
templating.hideDeleteButton(0);
assert.ok($(fileContainer0).find(".qq-upload-delete-selector").hasClass(HIDE_CSS));

templating.showDeleteButton(0);
assert.ok(!$(fileContainer0).find(".qq-upload-delete-selector").hasClass(HIDE_CSS));
});

it("hides and shows cancel link", function() {
templating.hideCancel(0);
assert.ok($(fileContainer0).find(".qq-upload-cancel-selector").hasClass(HIDE_CSS));

templating.showCancel(0);
assert.ok(!$(fileContainer0).find(".qq-upload-cancel-selector").hasClass(HIDE_CSS));
});

it("hides and shows edit icon", function() {
templating.hideEditIcon(0);
assert.ok(!$(fileContainer0).find(".qq-edit-filename-icon-selector").hasClass(EDITABLE_CSS));

templating.showEditIcon(0);
assert.ok($(fileContainer0).find(".qq-edit-filename-icon-selector").hasClass(EDITABLE_CSS));
});

it("is able to find the file ID given a button element", function() {
var deleteButtonEl, cancelButtonEl, retryButtonEl;
deleteButtonEl = $(fileContainer0).find(".qq-upload-delete-selector")[0];
cancelButtonEl = $(fileContainer0).find(".qq-upload-cancel-selector")[0];
retryButtonEl = $(fileContainer0).find(".qq-upload-retry-selector")[0];

assert.equal(templating.getFileId(deleteButtonEl), 0, "Button 1 level deep");
assert.equal(templating.getFileId(cancelButtonEl), 0, "Button 2 levels deep");
assert.equal(templating.getFileId(retryButtonEl), 0, "Button 3 levels deep");
});

});
});

0 comments on commit 29ae7d8

Please sign in to comment.