Skip to content
This repository has been archived by the owner on Apr 2, 2021. It is now read-only.

Image selection #641

Merged
merged 46 commits into from Dec 30, 2014
Merged

Image selection #641

merged 46 commits into from Dec 30, 2014

Conversation

mightyiam
Copy link
Contributor

Removes _selectedImage.

selection = wym.selection();
imageRange = rangy.createRangyRange();
imageRange.selectNode(evt.target);
selection.setSingleRange(imageRange);
}
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crux of this PR.

Simply selects, using Rangy, the image that was clicked upon.

No more unsetting & setting wym._selectedImage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a simple mechanism for selecting images. It could be more pretty, but it is simple and works in all browsers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous mechanism didn't work well enough, because selection wasn't set to the image and there was otherwise no indication that the image was indeed selected. Users don't look at the debugger and put a watch on jQuery.wymeditors(0)._selectedImage, ya know?

$container = jQuery(wym.selectedContainer());
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that toggling the class of an img isn't possible.

Does it seem important? Regression?

I notice that we don't have tests for toggleClass.

"Image is the only selected node"
);
}
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test for image selection, instead of the test for _selectedImage.

manipulationTestHelper({
startHtml: noChangeHtml,
manipulationFunc: function (wymeditor) {
wymeditor.$body().find("img").mousedown();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manipulation in this test is a jQuery.fn.mousedown() on the image.

If selection encompasses exactly a single image, returns that image.
Otherwise returns `false`.
*/
WYMeditor.editor.prototype.getSelectedImage = function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New!

wymeditor = jQuery.wymeditors(0);

// Stop QUnit from running the next test
stop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than the deprecated stop() and start(), why not use assert.async and done()? This line would be var done = assert.async(); and the start() line would be done() I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would require an upgrade of QUnit, I believe.

module("images-selection", {setup: prepareUnitTestModule});

test("Image is selected via `mouseup` in non pre-7 Trident", function () {
manipulationTestHelper({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr you're right. The async: true here was just left by me here. This serves to show that turning that switch on by mistake could result in a false passing test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. What can we do about that? Seems like expect() is the thing designed to avoid those kind of false positives.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See fix in manipulationTestHelper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

* assertions. This can only be used when a single manipulation cause
* is provided (for example, only `manipulationFunc`).
* For example:
* ```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr replaced resume with resumeManipulationTestHelper in the example below. Not runAssertions because it is more than assertions—it is also undo/redo operations. And also, by the time it returns, some assertions were already executed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

return asyncResumeFunc;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr you're right. This is much cleaner. I thought about writing it like this, at first, but setting it inside a while loop statement didn't seem reasonable, until now that I realized that in the case when a.async === true, that loop will execute once.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this code functions correctly, it reads poorly. There's no reason to return here if we're not using async (even though the caller should just ignore the return result). Let's put it inside an if (a.async === true) block.

return;
// Expectancy incremented here in order to fail tests that specify
// `async` but do not call the `asyncResumeFunc`.
expect(expect() + 1);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the fix for the case where a test specifies async: true but does not call the returned function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever fix!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

// Unwrap
wymeditor._selectSingleNode = _selectSingleNode;
// Resume `manipulationTestHelper`
resumeManipulationTestHelper();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resume() function when called in your wrapped _selectSingleNode isn't yet set to the return value of manipulationTestHelper because that function hasn't yet returned. Having a hard time making heads or tails of this.

  1. The scope of this call is a function definition, an assignment to wymeditor._selectSingleNode. wymeditor._selectSingleNode will be called later, triggered by pre–7 Trident's mouseup.
  2. It is ok that resumeManipulationTestHelper(), which is not defined (undefined) at the time of this function's definition, is included inside, because it is currently set to undefined. If you try to call it now, it would be like doing undefined() and that will probably yield something like "undefined does not have property call" or something like that.
  3. Since the call to wymeditor._selectSingleNode is inside a setTimeout (with zero time) it will run only (and probably immediately) after resumeManipulationTestHelper had returned. Thus, in the scope of the anonymous function of this test, by that time, when it does run, in this scope resumeManipulationTestHelper would have already been the return value of manipulationTestHelper.

I hope this is clear enough :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// we test things that have to do with the browser's DOM API. Removing this
// or setting it to `true` will almost certainly cause all tests of
// asynchronous things to fail or the test runner to stop and not continue.
sinon.config.useFakeTimers = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr this explains it, right?

// undesired resize handles.
// Wrapping the selection call in an immediate `setTimeout` makes
// reasonably certain that the "control selection" will be very quickly
// replaced by our desired, regular selection.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr at your request, a comment here.

// https://github.com/wymeditor/wymeditor/pull/641
window.setTimeout(function () {
wym._selectSingleNode(evt.target);
}, 0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr regarding zero–time setTimeout, I highly recommend this talk.

In short, the browser holds a sequential stack of calls and it runs them ASAP, sequentially. When one call ends, the next can start.

setTimeout adds a call on top of that stack, after the specified time. A zero time means that it gets throw on that stack immediately.

In our case, when an image gets a mouse event, some various named and unnamed events occur. Amongst them is mouseup, which we have a handler for. In pre–7 Trident, that handler throws a selection action on top of the call queue stack, with setTimeout with zero time. After that, the controlselect is triggered and the resize handles appear. After that, synchronous code had finished running and the browser's event loop executes the next call in the queue stack, which is almost certainly the selection call which we had previously thrown there from mouseup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man, that sounds like bad design. But it seems solid.

@winhamwr
Copy link
Member

Other than the two minor tweaks I suggested, this looks ready to merge!

@@ -64,6 +64,33 @@
* ```
* This example uses the `jquery.browser` plugin
* https://github.com/gabceb/jquery-browser-plugin
* `async`
* Optional; If this is `true` then after the manipulation is
* performed, assertions regarding the results are not synchronously
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comma here.

}

if (a.async === true) {
return asyncResumeFunc;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winhamwr you asked for this return to be conditional. Here it is. Thanks.

mightyiam added a commit that referenced this pull request Dec 30, 2014
@mightyiam mightyiam merged commit 6597aca into master Dec 30, 2014
@mightyiam mightyiam deleted the selectImagesWithRangy branch December 30, 2014 14:26
@mightyiam
Copy link
Contributor Author

Great! Thank you!

@mightyiam mightyiam mentioned this pull request Oct 7, 2015
4 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants