-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Range actions #5052
Add Range actions #5052
Conversation
This PR will fix: [IDEA] Add actions parameter to "range widget" #5029 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @pmario
core/modules/widgets/range.js
Outdated
@@ -92,16 +173,30 @@ RangeWidget.prototype.handleInputEvent = function(event) { | |||
Compute the internal state of the widget | |||
*/ | |||
RangeWidget.prototype.execute = function() { | |||
// TODO remove the next 2 lines once IE is gone! | |||
this.mouseUp = true; // Needed for IE10 | |||
this.isIE = (/msie|trident/i.test(window.navigator.userAgent)) ? true : false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The core already defines $tw.browser.isIE
core/modules/widgets/range.js
Outdated
this.actionsMouseUp = this.getAttribute("actionsMouseUp",""); | ||
// Change only fires, if start-value is different to end-value | ||
this.actionsChange = this.getAttribute("actionsChange",""); | ||
// input fires very often!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a small thing but we do try to capitalise comments as sentence case to keep things neat
core/modules/widgets/range.js
Outdated
// Next 3 only fire once! | ||
this.actionsMouseDown = this.getAttribute("actionsMouseDown",""); | ||
this.actionsMouseUp = this.getAttribute("actionsMouseUp",""); | ||
// Change only fires, if start-value is different to end-value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comma isn't necessary here
core/modules/widgets/range.js
Outdated
@@ -111,15 +206,17 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of | |||
*/ | |||
RangeWidget.prototype.refresh = function(changedTiddlers) { | |||
var changedAttributes = this.computeAttributes(); | |||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes['min'] || changedAttributes['max'] || changedAttributes['increment'] || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.disabled) { | |||
if( changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes['min'] || changedAttributes['max'] || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind the condition being split to multiple lines, but we wouldn't usually do it by adding whitespace between the opening parenthesis and the condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using tab characters for alignment (the "if(" line is really "if(→" where → represents a tab character) is a bad idea, because it ensures that the code will only be aligned if someone else uses tab settings that are the same size as yours, thereby removing the one big advantage of tabs for indentation (that people can set their own tab settings). Here you have the right idea by putting a tab after the "if(", so that people with either 4-space or 8-space tab settings will see the code correctly aligned. But if someone has their tab size set to 2 spaces, then this is no longer aligned.
If you are using tabs for indentation, the only correct approach is "tabs for indentation, spaces for alignment". The second line of the if(
condition should be indented the same level as the if(
(here that's two tab characters for two indentation levels), with three spaces after the two tab characters to line up the changedAttributes
text. I.e., the second line should look like →→···changedAttributes['increment']
, where → represents a tab character and · represents a space. That is the only correct way to do alignment when you have used tabs for indentation, because it ensures that the aligned lines will stay aligned in everyone's editor no matter what tab settings they use.
And the big problem with "tabs for indentation, spaces for alignment" is that most editors are not smart about it, and will mess it up. Which means that if you've aligned your code, you then have to pay careful attention to your whitespace every time you edit that line. Which is a lot of fiddly work that I, for one, don't care for, and that's why my personal preference is to use space characters, not tabs, for indentation.
There are really only four options:
- Use tab characters naively, for both indentation and alignment, and end up with code that looks different when different editor settings are applied.
- Use tab characters intelligently, using tabs for indentation and spaces for alignment, and end up paying far more attention to whitespace than it's worth.
- Use tab characters for indentation and forget trying to align subsequent lines. The rule for function parameters, second lines of "if()" conditions, and so on, is "just indent one more level with one extra tab character". This will still look reasonably good on everyone's editor, and everyone can use their favorite indentation level by tweaking their editor's tab settings.
- Use space characters for indentation. This allows you to align function parameters, "if()" conditions, and so on, but other coders won't get to use their favorite indentation level and must conform to the indentation level the project has chosen.
My personal preference is for 4. 1 is just bad, 2 causes you to pay way more attention to whitespace than it's worth, and 3 sacrifices alignment, which is a useful tool in many situations. So I'd rather choose option 4 and sacrifice the ability to use my preferred indentation, instead using whatever the project has settled on.
Point is, it's up to you whether you choose 2 or 3 here (4 is not an option since the TiddlyWiki project uses tabs for indentation), but please don't choose option 1. To choose option 2, write your continuation lines as →→···condition
. To choose option 3, write your continuation lines as →→→condition
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Use space characters for indentation. This allows you to align function parameters, "if()" conditions, and so on, but other coders won't get to use their favorite indentation level and must conform to the indentation level the project has chosen.
TW doesn't compress or minify the source code. That makes it easy for every user to start to read the code and start to debug it.
That means the empty.html would contain 4 times more indentation spaces as used with tabs. This would considerably increase the basic file size. So atm TW uses tabs for a reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, TW is one of the few projects where the fact that tabs take less storage than spaces is actually relevant. Most of the time compression makes the space savings of tab characters irrelevant, but in TW it actually matters. I wasn't actually arguing that TW should change (I did mention that option 4 is not an option in this case), I was just trying to list all available options for dealing with indentation & alignment. I agree that using tabs for TW makes sense since it isn't minified (though it probably is compressed, given that most servers default to gzipping HTML for delivery).
Though it is actually a little hard for every user to read the code, since when you download TW in HTML form via the big "Download Empty" button, the HTML form has had to store the code in a way that looks like "{\n\t\t\t\tresults.push(title);\n\t\t\t}" and so on. Granted, anyone who knows enough about Javascript to want to modify that code also probably knows how to do a search-and-replace to turn "\n" into a real newline and "\t" into a real tab. But there is a small barrier to entry; it's not quite the ideal situation that you described. :-)
{name:"mousedown", handlerObject:this, handlerMethod:"handleMouseDownEvent"}, | ||
{name:"mouseup", handlerObject:this, handlerMethod:"handleMouseUpEvent"}, | ||
{name:"change", handlerObject:this, handlerMethod:"handleChangeEvent"}, | ||
{name:"input", handlerObject:this, handlerMethod:"handleInputEvent"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather see this list built dynamically based on whether there's an attribute specified for that event. Something like this:
var eventList = [];
if(this.actionsMouseDown) {
eventList.push({name:"mousedown", handlerObject:this, handlerMethod:"handleMouseDownEvent"});
}
// Similar for this.actionsMouseUp and this.actionsChange
if(this.actionsInput) {
eventList.push({name:"input", handlerObject:this, handlerMethod:"handleInputEvent"});
}
$tw.utils.addEventListeners(this.inputDomNode, eventList);
The advantage is that if the end user doesn't care about the input
event, we don't need to call a dozen times into an event handler that will end up doing nothing. There is a performance cost to the browser firing events, even if the event handler returns quickly. If we can avoid hooking up the event at all, the browser can optimize and not fire the event because it knows nothing is going to handle the event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point @rmunn
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with input and change event are, that they have to be fired, even if there is no action. Otherwise the target tiddler wouldn't be updated. It doesn't matter if there are actions defined or not. ... Input is fired on all modern browsers and change is fired in IE.
You are right for MouseDown and MouseUp. ATM they are only there for handling actions atm. ... BUT we don't know if this will change in the future.
Events are only fired if user wants interaction. So IMO there is no real performance overhead, without user interaction. So I'd vote for simple code here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot something. MouseUp is needed for IE to trigger the change event. So it doesn't matter if actions are defined.
@Jermolene Should I add the |
@pmario I think a method on the base widget class makes sense. Perhaps |
Latest commit 8b25443 uses |
core/modules/widgets/range.js
Outdated
@@ -111,15 +202,17 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of | |||
*/ | |||
RangeWidget.prototype.refresh = function(changedTiddlers) { | |||
var changedAttributes = this.computeAttributes(); | |||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes['min'] || changedAttributes['max'] || changedAttributes['increment'] || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.disabled) { | |||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes['min'] || changedAttributes['max'] || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmario if we need to call refreshSelf()
if any attributes have changed, it is easier to just check the count:
if($tw.utils.count(changedAttributes) > 0) {
this.refreshSelf();
return true;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this the case for every widget?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmario We just can't use this trick when we only want to call refreshSelf()
on some attributes changing but not for others. So there is probably room for some refactoring and optimization in other core widgets.
core/modules/widgets/range.js
Outdated
// Trigger actions | ||
if(this.actionsInput) { | ||
// "tiddler" parameter may be missing. See .execute() below | ||
var variables = this.prepareAttributes() // TODO this line will go into the function call below. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently handleEvent()
does debouncing and only calls wiki.setText
if the value of the widget has changed. We should similarly debounce the actions, at least the ones for events like input
that fire very frequently:
if(this.getValue() !== this.inputDomNode.value) {
this.invokeActionString(this.actionsInput,this,event,variables);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
input or change(on IE) are only fired, if the value has been changed. ... IMO this is no debouncing.
The line was introduced with commit e84c422 ... But I don't see a difference, if I remove it. ... @Jermolene Do you remember the problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmario according to the spec you should be right. I thought I ran into some issue with this last I worked on this widget. I can do some tests to confirm tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmario I can confirm that the debouncing does not seem necessary on the input event. In both Chrome and FF I cannot get the event to fire more than once per change of value.
@pmario @Jermolene @rmunn I have some concerns regarding usability from the point of view of end users, and a question in this regard please: @rmunn has given some great examples of why having separate actions for the I am worried about how accessible this will be to non-technical users who need to distinguish between 4 types of actions:
If we are handling those extra events ( Furthermore the current attribute names are perhaps too developer centric, especially if there are four of them. These are user facing widget attributes and not internal variables or method names. |
I'm inclined to agree with @saqimtiaz. If we do want to trap mousedown/move/up, then I think we might be better off making a generic event trapping widget that traps events within its content. That widget could be wrapped around any other widget without that widget being aware of the event handling. |
I like this idea! In many ways the We could generalize this to a |
I've got a very simple prototype |
Here's the ticket: #5074 |
I was thinking about it too. So I wanted to implement a It's made sure, that "start" and "stop" is only called once. and action is called whenever the handle is moved. |
It has 3 actions now:
|
Latest Version: range-actions.zip All console.log() and redundant code is removed. see commit: 0111333 |
} | ||
} | ||
} | ||
return value; | ||
}; | ||
|
||
RangeWidget.prototype.prepareAttributes = function(options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this name is not intuitive. In fact we are processing attributes to prepare variables, rather than preparing attributes.
Perhaps getActionVariables()
so that the context is easier to undertand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getActionVariables
would be more consistent with other usages in the core.
I was getting some inconsistencies with filter operators that expect strings.
Bump. |
Will close this one and create a new and simplified version. |
This is WIP and contains some
console.log()
functions so you can see, what's going on with F12There some redundant code to simplify debugging. ... See code-comments!
This PR adds 4 actions:
actionsMouseDown
actionsMouseUp
actionsInput
actionsChange
It behaves the same way in modern browsers and contains some lines of code to behave well with IE11.
I did test it with IE11 wondows 10, FF latest, Edge latest. ..
Go to Latest Version: