Range.compareBoundaryPoints() generates strange results - javascript

I am using a Range object as a limiter on another Range object representing the current selection, such that anything that is inside the selected range but is outside the boundaries of the limiting range is trimmed off. To compare the two Ranges' positions, I'm using the Range.compareBoundaryPoints() function, but the results make no sense.
The element I'm working with looks like this:
<p id="myBlock" contenteditable="true">
Hello hello hello, this is a great big block of test text.
</p>
The ranges are defined and their positional relationship retrieved as follows:
var limitingRange = document.createRange();
limitingRange.selectNodeContents($('#myBlock')[0]);
var selectedRange = window.getSelection().getRangeAt(0).cloneRange(); //Use whatever method is supported by the browser to get the Range
var endToStart = limitingRange.compareBoundaryPoints(Range.END_TO_START, selectedRange);
var startToEnd = limitingRange.compareBoundaryPoints(Range.START_TO_END, selectedRange);
var startToStart = limitingRange.compareBoundaryPoints(Range.START_TO_START, selectedRange);
var endToEnd = limitingRange.compareBoundaryPoints(Range.END_TO_END, selectedRange);
I then select the word "this" in the text and run the function. My ranges look like this (I'm inventing the .start and .end notation to try and make my description clearer):
(limitingRange.start)Hello hello hello, (selectedRange.start)this(selectedRange.end) is a great big block of test text.(limitingRange.end)
The resulting values are:
endToStart = -1
startToEnd = 1
startToStart = -1
endToEnd = 1
I am baffled as to how to interpret the results of the function. Based on the specification it seems like, for example, endToStart should contain -1 if the end of selectedRange comes before the start of limitingRange, but that's obviously not the case. What's going on here?

I'm answering my own question: Typing out what was going on led me to actually understand the function's behavior, which really is strange.
The result of a.compareBoundaryPoints(X_TO_Y, b) is the comparison of a.Y to b.X. -1 if a.Y comes before b.X, 0 if they are at the same position, and 1 if it comes after.
So in endToStart in the above example, limitingRange.start is being compared to selectedRange.end, with -1 indicating that limitingRange.start comes first.
startToEnd: limitingRange.end comes after selectedRange.start, so the result is 1.
startToStart: limitingRange.start comes before selectedRange.start, so the result is -1.
endToEnd: limitingRange.end comes after selectedRange.end, so the result is 1
Leaving me with only the question: Why does this function behave this way? The names of the constants are the opposite of what one would intuitively expect.

Related

Getting empty array on contentControls officejs

Word.run( async (context) => {
var searchResults = context.document.body.search('moslim');
context.load(searchResults);
return context.sync().then(async function () {
if (searchResults.items.length > 0) {
// Get the first search result
var range = searchResults.items[0].getRange();
// range.clear();
// Insert a content control around the search result
var contentControl = range.insertContentControl();
// Set the tag of the content control
contentControl.tag = 'your tag';
contentControl.insertText(searchResults.items[0].text.toString(), 'Replace');
// Load the content control
context.load(contentControl);
await context.sync()
var getcontentControl = range.contentControls
context.load(getcontentControl)
await context.sync()
console.log(getcontentControl.items)
}
});
});
last console.log gives empty array but contentcontrol has been added the ms word document.
Can anyone help on this what I did wrong?
I don't think you did anything "wrong" It's just that, despite the fact that you are calling a method called insertContentControl on your Range object, office-js surrounds the range with the control (at least in the case you mention) in such a way that it is not actually added to the range.
The question is what you are actually trying to achieve and how to do that. (It wasn't obvious from the Question as it stands).
If you did the equivalent thing in VBA to your found text, it might be
myrange.ContentControls.Add wdContentControlRichText
In that case, in VBA the content control is in myrange, i.e.
myrange.ContentControls.Count
returns 1. But it wouldn't be in myrange if myrange did not "cover" any characters (i.e. myrange.Start=myrange.End )
I made a simple modification to your office-js code to select the range it found. I also commented out the code that puts text in the control
var contentControl = range.insertContentControl();
range.select();
// Set the tag of the content control
contentControl.tag = 'your tag';
//contentControl.insertText(searchResults.items[0].text.toString(), 'Replace');
If I run your example code with those changes, it's quite easy to find out where the VBA WOrd object model thinks the control and the range are:
Sub rangeandccinfo()
Debug.Print ActiveDocument.ContentControls(1).Range.Start, ActiveDocument.ContentControls(1).Range.End
'In office-js we set by using the Range. So they should really be the same (except maybe we shouldn't
'expect office-js to behave the same as the traditional object model.
Debug.Print Selection.Start, Selection.End
Debug.Print Selection.Range.ContentControls.count
Debug.Print Selection.Range.Information(wdInContentControl)
End Sub
In my experiment, the results are like this
3 9
3 9
0
True
But if I just select the same word manually and insert the CC using VBA like this:
Sub ccrange()
Dim r As Range
Debug.Print Selection.Start, Selection.End
Set r = Selection.Range
'should be the same
Debug.Print r.Start, r.End
r.ContentControls.Add wdContentControlRichText
Debug.Print r.Start, r.End
r.Select
Set r = Nothing
End Sub
then run the rangeandccinfo() Sub, I see this:
3 9
2 9
1
True
i.e. in office-js, it looks as if the range and the range of the Content Control have the same Start and End, whereas in VBA, the Range starts one character before the Range of the Content Control, and that seems to be what make the VBA count of COntent COntrols in the Range 1 rather than 0.
If you actually click at the beginning of the text in a Content Control and click the left arrow button (in a document with left-to-right text) you will see that the insertion point does not appear to move. And yet its Range does change.
So it's at least partly an implementation choice how to do this. Personally, I have found it quite difficult to work with the way VBA does it (its difficult to work out what's inside what). It's quite possible that the office-js team encountered a similar problem and just decided to do it a way that made more sense to them.

Updating Variables Within Array After the Array Has been Declared (Javascript)

I haven't found this answer anywhere, and have been on the lookout for a few months, so my apologies if I'm overlooking something that should be obvious. Self-taught and came upon a rather vexing gap in my knowledge here.
In an rather complex yarn of connected pieces, I have two globally-scoped (basically static) variables and an array of character types outside of the main onclick function, as such:
var missingWut = ["child","spouse","talisman","relic","sock"];
var rdmmissingWut;
var pronounA = "he";
var charTypes = [
["goatherd",pronounA+" wants to find a missing goat","kind"],
["shepherd",pronounA+" wants to find a missing sheep","cruel"],
["detective",pronounA+" wants to find a missing "+missingWut[rdmmissingWut],"spidery"],
...,
..., //this goes on for awhile; the array is currently 500 items long and has way more subindexes than I wanted/needed to include in this example.
];
We've declared the variable names in the line above, but obviously rdmmissingWut is undefined at this point.
We then - for the sake of memory - go on to define rdmmissingWut inside the function, thereby updating its value from undefined to a random index number:
rdmmissingWut = Math.floor(Math.random()*missingWut.length);
rdmcharType = Math.floor(Math.random()*charTypes.length);
before assigning a random charType index to character 1 (char1).
var char1 = charTypes[rdmcharType];
My question is this -
Is there a way to update the variable value within the array - after I've updated the variable - without redefining the entire array?
One could obviously just reiterate the definition of the array, at which point it would update all variable values with their current value, but that seems really clumsy, cluttered and inefficient.
Another use case (with the same issue):
I want to use this same chartypes array to randomly roll a character type for character 2 (char2) - and eventually, char3 & char4, as well. But let's say char2 (or 3 or 4) is female. To do this, after char1 was defined, I would then need to update the value of pronounA to "she" and thereupon update the pronounA definition in every instance within the charTypes array before selecting a random charTypes index for her - correct? What is the best way to accomplish this? I'm sure there must be some elegant solution that I'm just ignorant of.
Thanks for your help.
You'll be needing to evaluate that variable every time you run through your array, so I'd recommend a placeholder that can be replaced with .replaceAll
var missingWut = ["child", "spouse", "talisman", "relic", "sock"];
var rdmmissingWut;
var pronounA = "he";
var charTypes = [
["goatherd", pronounA + " wants to find a missing goat", "kind"],
["shepherd", pronounA + " wants to find a missing sheep", "cruel"],
["detective", pronounA + " wants to find a missing _missingWut_", "spidery"]
];
function getMissingWut() {
return missingWut[rdmmissingWut || 0]; // this uses zero incase the value hasn't been updated
}
console.log(charTypes.flat().join("\n").replaceAll(/_missingWut_/g, getMissingWut()))
rdmmissingWut = 4
console.log(charTypes.flat().join("\n").replaceAll(/_missingWut_/g, getMissingWut()))

Shorthand code for multiple "or" statements inside the if statement? [duplicate]

I have a group of strings in Javascript and I need to write a function that detects if another specific string belongs to this group or not.
What is the fastest way to achieve this? Is it alright to put the group of values into an array, and then write a function that searches through the array?
I think if I keep the values sorted and do a binary search, it should work fast enough. Or is there some other smart way of doing this, which can work faster?
Use a hash table, and do this:
// Initialise the set
mySet = {};
// Add to the set
mySet["some string value"] = true;
...
// Test if a value is in the set:
if (testValue in mySet) {
alert(testValue + " is in the set");
} else {
alert(testValue + " is not in the set");
}
You can use an object like so:
// prepare a mock-up object
setOfValues = {};
for (var i = 0; i < 100; i++)
setOfValues["example value " + i] = true;
// check for existence
if (setOfValues["example value 99"]); // true
if (setOfValues["example value 101"]); // undefined, essentially: false
This takes advantage of the fact that objects are implemented as associative arrays. How fast that is depends on your data and the JavaScript engine implementation, but you can do some performance testing easily to compare against other variants of doing it.
If a value can occur more than once in your set and the "how often" is important to you, you can also use an incrementing number in place of the boolean I used for my example.
A comment to the above mentioned hash solutions.
Actually the {} creates an object (also mentioned above) which can lead to some side-effects.
One of them is that your "hash" is already pre-populated with the default object methods.
So "toString" in setOfValues will be true (at least in Firefox).
You can prepend another character e.g. "." to your strings to work around this problem or use the Hash object provided by the "prototype" library.
Stumbled across this and realized the answers are out of date. In this day and age, you should not be implementing sets using hashtables except in corner cases. You should use sets.
For example:
> let set = new Set();
> set.add('red')
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
Refer to this SO post for more examples and discussion: Ways to create a Set in JavaScript?
A possible way, particularly efficient if the set is immutable, but is still usable with a variable set:
var haystack = "monday tuesday wednesday thursday friday saturday sunday";
var needle = "Friday";
if (haystack.indexOf(needle.toLowerCase()) >= 0) alert("Found!");
Of course, you might need to change the separator depending on the strings you have to put there...
A more robust variant can include bounds to ensure neither "day wed" nor "day" can match positively:
var haystack = "!monday!tuesday!wednesday!thursday!friday!saturday!sunday!";
var needle = "Friday";
if (haystack.indexOf('!' + needle.toLowerCase() + '!') >= 0) alert("Found!");
Might be not needed if the input is sure (eg. out of database, etc.).
I used that in a Greasemonkey script, with the advantage of using the haystack directly out of GM's storage.
Using a hash table might be a quicker option.
Whatever option you go for its definitely worth testing out its performance against the alternatives you consider.
Depends on how much values there are.
If there are a few values (less than 10 to 50), searching through the array may be ok. A hash table might be overkill.
If you have lots of values, a hash table is the best option. It requires less work than sorting the values and doing a binary search.
I know it is an old post. But to detect if a value is in a set of values we can manipulate through array indexOf() which searches and detects the present of the value
var myString="this is my large string set";
var myStr=myString.split(' ');
console.log('myStr contains "my" = '+ (myStr.indexOf('my')>=0));
console.log('myStr contains "your" = '+ (myStr.indexOf('your')>=0));
console.log('integer example : [1, 2, 5, 3] contains 5 = '+ ([1, 2, 5, 3].indexOf(5)>=0));
You can use ES6 includes.
var string = "The quick brown fox jumps over the lazy dog.",
substring = "lazy dog";
console.log(string.includes(substring));

Flash/Animate CC Tween Formatted Number

I'm using Animate CC (the erstwhile Flash CC) to do some ads that I'm exporting in HTML5 format (<canvas> and CreateJS stuff). They're working quite nicely overall.
I have a formatted number, in a Static Text box, like so: 5,000,000 and I want to tween it to, say, 20,000, over the course of 30 frames. I want to tween the same text to 5,000 and 1,000,000 and so on throughout the course of my scene.
In my limited Animate CC experience, I've managed to avoid using any Javascript, but I imagine that I will need to now. So, my question: how do I do this?
My thoughts on ways of doing this:
Since I'm using CreateJS, which has the TweenJS library as part of it, maybe I can just use that for tweening? Make little Actions at different points of my timeline? Not sure how all that works, and a lot of the references online are for ActionScript 3 or even AS2. Sample code would be appreciated.
If I do get into Javascript, there's the question of how I would do the number formatting. I could tween the number as 5000000 -> 20000 and on each frame update insert commas, that's one way of doing it. But to make matters more complex, these ads are going to be translated, and different locales come into the mix. So in English you get 5,000,000 and in German would you have 5.000.000, of course.
Since we're talking Javascript in the browser, I'm aware of the method Number.prototype.toLocaleString() which does the following:
The toLocaleString() method returns a string with a language sensitive
representation of this number.
That seems like it would do the trick, but then I have to worry about browser compatibility and what happens if I don't specify a locale. Ideally, since the German ads would only be displayed to people who had a German locale on their browser/OS, I could just call the method without any locale specified, and it would read it off the user's computer. I suppose it's possible to have the scenario where a German person is seeing an English ad, but I'm not that worried about it.
However, on the MDN page for toLocaleString() it has this big warning about earlier versions of FF defaulting to Western Arabic digits, so it makes me doubt the use of the method entirely.
Finally, I have the interesting fact that the translators will almost certainly take 5,000,000 and convert it into 5.000.000 for German. So it may be possible to avoid the use of toLocaleString() since I'll already have localized text. So if it were possible to write a simple Javascript function that could tween arbitrarily formatted numbers, I think that would do the trick. Perhaps:
Take the starting number and rip formatting out of it, save it
Tween the number
On each frame update, inject the formatting back into it
Probably not that hard from a JS perspective, but where I get stumped is how the heck I would do this in Animate/Flash and/or with CreateJS/TweenJS?
As far as tweening a formatted number using TweenJS, you can just tween a non-formatted number, and on "change", create a formatted version to do what you need:
createjs.Tween.get(obj, {loop:true})
.to({val:10000}, 4000)
.to({val:0}, 4000)
.on("change", formatNumber);
function formatNumber(event) {
// Round and format
var formattedNumber = (obj.val|0).toLocaleString();
}
Here is a simple fiddle: http://jsfiddle.net/m3req5g5/
Although Lanny gave some good data, I wanted to lay out exactly what I ended up doing to get this working.
First, you need to get a reference to the object you're going to be tweening in some way. When you make an Action in Flash and write Javascript, this is bound to the Stage, or to the MovieClip or Graphic that you're editing:
http://createjs.com/html5ads/#Scope
You can access objects using their instance names which are defined in Flash on the Properties of the object, once you've placed it on the Stage. Some sources online said that it was based on the symbol name or some such, but I haven't found that to be the case.
// Get a reference to the object you want to tween
var obj = this.anInstanceName;
Note that, if you want to access something that's inside a MovieClip, you will need to give your MovieClip on the stage an instance name, and then go inside the MovieClip and give an instance name to your target object. Then you can just walk down the hierarchy:
// Get a reference to the nested object you want to tween.
var obj = this.movieClipInstanceName.nestedInstanceName;
Now you can tween any numeric property of the object in question. For me, because I wanted to tween the text, I set an additional property on the object and tweened that, then formatted and copied it over into the text property as I went along.
It was useful to be able to specify how many frames the tween lasted, rather than the milliseconds, so I passed the useTicks flag.
obj.counter = 0;
createjs.Tween.get(obj, {useTicks: true})
.to({counter: 100}, 30) // <- 30 frames, this number is ms without useTicks
.on("change", formatNumber);
function formatNumber(event) {
obj.text = obj.counter.toLocaleString();
}
The above is generally applicable. Otherwise, here's the working code that I ended up using. It should be able to be dropped into a Flash Action in an HTML5 Canvas project and just work.
// Figures for tweening
var textStart = "2,000";
var textEnd = "6,000,000";
// Locate our target text box
var target = this.myTextBox; // replace "myTextBox" with your instance name
// Get our formatting data and so on
var data = this.getFormatData(textStart);
// Set up the text box
target.number = data.number;
target.text = textStart;
// Get the raw number we're tweening to
var endNumber = this.getFormatData(textEnd).number;
// Create the tween
createjs.Tween.get(target, {useTicks: true})
.to({number:endNumber}, 30)
.on("change", format);
//Formatting function, gets called repeatedly for each frame
function format(event) {
var rounded = Math.round(target.number);
var formatted = formatNumber(rounded, data.format);
target.text = formatted;
}
// UTILITY FUNCTIONS:
// Takes "###,###,###" or somesuch
// Returns a raw number and a formatting object
function getFormatData(formattedNumber) {
var toString = "" + formattedNumber; // in case it's not a string
var reversed = toString.split('').reverse(); // get a reversed array
// now walk (backwards) through the array and remove formatting
var formatChars = {};
for (var i = reversed.length-1; i >= 0; i--) {
var c = reversed[i];
var isnum = /^\d$/.test(c);
if (!isnum) {
formatChars[i] = c;
reversed.splice(i, 1);
}
}
// get the actual number
var number = parseInt(reversed.reverse().join(''));
// return the data
var result = {number: number, format: formatChars};
return result;
}
// Takes a raw number and a formatting object and produces a formatted number
formatNumber(number, format) {
var toString = '' + number;
var reversed = toString.split('').reverse();
var index = 0;
while (index < reversed.length) {
if (format[index]) {
reversed.splice(index, 0, format[index]);
}
index++;
}
var finished = reversed.reverse().join('');
return finished;
}
This fiddle demos the formatting and has a bit more of an explanation in the comments.
There are other ways of doing this, for sure, such as toLocaleString(), but this fit my exact requirements. Hopefully it'll help someone else.

JavaScript Short Circuit Logic

After seeing some examples online, I've collected two different explanations:
Ex: var x = A || B;
If A exists and B does not, left side is returned.
If A exists and B exists , return right side (last evaluated value).
Based on that logic, I would assume that x would return: v.item(0).click(). But when testing it x first returned B then A, aka fired B then fired A as well. Why? (http://jsfiddle.net/nysteve/QHumL/59/)
HTML:
<div class="indeed-apply-button" onclick="alert('BOOM BUTTON');">boo</div>
<div class='view_job_link' onclick="alert('BOOM LINK');">boo</div>
JavaScript
var v = document.getElementsByClassName('view_job_link');
var i = document.getElementsByClassName('indeed-apply-button');
var x = v.item(0).click() || i.item(0).click();
EDIT 1:02 PM 10/10/2013
Did not mention my true intentions, but based on the answer and discussion, my goal was essentially to convert the following piece of code into the JavaScript code I originally mentioned.
var v = document.getElementsByClassName('view_job_link');
var i = document.getElementsByClassName('indeed-apply-button');
if(v){v.item(0).click();}
else if(i){i.item(0).click();}
else{}
In the code above, how would you read if(v){v.item(0).click();} vs. the short-circuit?
Neither of your two descriptions are accurate.
var x = A || B;
What that does is:
Evaluate "A". Call the result VA.
If VA is not null, undefined, 0, NaN, false, or the empty string, then the value of the overall expression is VA and evaluation stops.
Evaluate "B". Call the result VB. That value, VB, is the value of the expression.
Your test code first tests the return value of calling the "click" function on the first element and then the second. Those functions both return undefined, so the subexpressions on both sides of the || are evaluated. That is, the first call gets VA and it's undefined, so that means that the other side will be evaluated too, and that'll be the value of the expression. (It's going to come out undefined.)
edit — OK now that you've added more to the answer, I think I see what you're up to.
In your actual code (or, I guess, the sample code that's closer to reality), you've got:
if(v){v.item(0).click();}
else if(i){i.item(0).click();}
else{}
That means something quite different than:
var x = v.item(0).click() || i.item(0).click();
Note that in the if statement version, it's explicitly checking "v". A test like that will perform a "truthy/falsy" test on the value of "v". In this case, it's really checking to make sure that "v" isn't either null or undefined. In the || version, however, there's no such explicit test for the "goodness" of variable "v"; it's just used directly. (If it happens to be null there, that'd result in a runtime error.)
A version of the actual code using || and && is possible, but in my opinion the existing code is clearer. However, just for discussion purposes, you could replace the if version with:
v && (v.item(0).click(), true) || i && i.item(0).click();
Personally I think that looks kind-of ugly.

Categories

Resources