EDIT:
I had a script that checked for CSS property support, in this case hyphenation support. It was quite long, but with the help of the respondents it was shortened to this:
var styleEngine = document.documentElement.style;
if ((!('hyphens' in styleEngine)) && (!('MozHyphens' in styleEngine)) && (!('WebkitHyphens' in styleEngine)) && (!('msHyphens' in styleEngine)))
alert('CSS hyphenation is not supported.');
else
alert('CSS hyphenation is supported.');
The reason for this edit, including the title, is to make this post more useful to people to people googling for a Javascript that checks for CSS property and/or value support. I later learned a few things about CSS support and its check which might be of interest. The first thing I learned is that Chrome at this moment (2014) says it supports -webkit-hyphens, but it does not support the most important value auto. That means that we have to check for property value support.
That can be done in two ways. The first is with the new CSS at-rule #supports. That is explained on https://developer.mozilla.org/en-US/docs/Web/CSS/#supports. The other way, with Javascript, is explained on http://ryanmorr.com/detecting-css-style-support/. And in the case of hyphenation, here is a Javascript polyfill for when proper hyphenation is not supported: https://code.google.com/p/hyphenator/.
Extract it to a function:
function hasStyle(prop) {
return prop in document.documentElement.style;
}
Then you can use:
if (!hasStyle('MozHyphens' && !hasStyle('msHyphens')) { ... }
Not too much shorter, but shorter enough (and encapsulated).
To be more concise, you can create another function:
function hasHyphens() {
return hasStyle('MozHypens') || hasStyle('msHyphens');
}
And then:
if (!hasHyphens()) { ... }
What about:
ValuesIntersect(valArray1, valArray2) {
var len = valArray2.length,
i = 0,
found = false;
while (i<len && !found) {
found = valArray2[i] in valArray1;
++i;
}
return found;
}
Use it like this:
if (!ValuesIntersect(['hyphens','MozHyphens', 'WebkitHyphens', 'msHyphens'], document.documentElement.style)) {...}
I find this is best for readability. When you're at that if statement, this clearly shows what you're checking for. The external function need not necessarily be small since it's rarely ever going to be checked. And it's generic enough to work in multiple places.
Related
DESCRIPTION
I am trying to create an inline blot for text highlighting. I know this feature is already present in quill. But in my implementation I would like to assign a different value to the html element depending on the type of highlighting that was assigned. Here's what I got:
let Inline = Quill.import('blots/inline');
class TextHighlight extends Inline {
static create(value) {
let node = super.create();
if(!value || value < 1) return node;
if(value == 5){
node.style.color = 'rgb(225, 225, 225)';
}
else {
node.style.borderRadius = '2px';
node.style.padding = '2px';
if(value == 1){ node.style.background = 'rgb(254, 255, 171)'; }
if(value == 2){ node.style.background = 'rgb(255, 171, 171)'; }
if(value == 3){ node.style.background = 'rgb(171, 207, 255)'; }
if(value == 4){ node.style.background = 'rgb(178, 255, 171)'; }
}
node.setAttribute('data-value' , value);
return node;
}
formats() {
console.log('#formats()');
let result = this.domNode.getAttribute('data-value');
return result ? result : 0;
}
}
TextHighlight.blotName = 'texthighlight';
TextHighlight.tagName = 'span';
My remove/add function:
function highlightSelectedText(value) {
if (value < 0 || value > 5) return false;
var range = quill.getSelection();
if (range && range.length > 0) {
if (value > 0) {
quill.formatText(
range.index,
range.length,
'texthighlight',
value,
true);
}
else {
quill.formatText(range.index, range.length, 'texthighlight', false, false);
}
}
}
And finally the creation of the Quill instance:
var toolbarOptions = {
container: '#toolbar-container',
handlers: {
'texthighlight': function (value) {
highlightSelectedText(value);
}
}
};
var quill = new Quill('#editor', {
theme: 'bubble',
modules: {
toolbar: toolbarOptions
}
});
The problems
Highlighted text snippets have the following Delta:
...
{
"attributes": {
"0": "3"
},
"insert": "highlighted text"
},
...
"texthighlight" should appear instead of 0, like:
...
{
"attributes": {
"texthighlight": "3"
},
"insert": "highlighted text"
},
...
If I apply formatting more than once, it starts accumulating, putting markup inside markup. For example:
<span class="texthighlight"><span class="texthighlight"><span class="texthighlight"><span class="texthighlight"></span></span></span></span>
The expected behavior is that only one highlight is present.
I cannot remove the formatting.
CONCLUSION
There is no doubt that I lack knowledge about how to properly implement this. I was able to create simpler blots in other situations, but now I'm really getting confused about overriding certain blot methods. For example, the following list shows which methods I mean, and what I understand about each:
static formats(node): Returns formats represented by domNode. Called on selection events when index is within formatted range.
formats(): Returns formats represented by this Blot. It is used for Delta generation. Called on selection events when index is within formatted range.
format(format , value): Applies formatting to this blot.
In the highlight implementation demonstrated, only formats() and create(value) are being called. I know there is an example of how each of these methods is implemented, but I am not getting the desired behavior. I think it's because I don't know how to exactly implement them. Could someone answer me what they really do, when they are called, and how they should behave (be implemented)?
Can somebody help me, please? :(
EDIT (Dec 18, 2019)
So... After hours of research, I was finally able to make the editor a little more correct. In this whole story I ended up creating a blot that is capable of formatting text with different styles depending on the value passed to formatting. You can even remove formatting by providing or omitting a value.
I was finally able to get an answer for item 2 and, as I thought, found it within the toolbar module. Regarding the formats methods and their differences, I think I could better understand it. I still don't understand why formats are called so often. I think I'll find the answer looking at quill core, but... The quill source code is very large as it involves multiple files.
I am putting here a more updated version of the code shown earlier. It is all commented, favoring anyone who wants to learn more about how to:
Define custom blots/formats,
Define blots that accept values, and behave in different ways
depending on the configured value.
Define custom toolbar buttons that respond and reflect the state of the
editor content.
I hope that this will enable people to better understand how this tool can be used, and to create greater interest and contribution with it.
Full Example on GitHub
ORIGINAL ANSWER
It seems that I somehow managed to get the desired result. I am putting an answer here, but I do not consider it correct yet, as it is not complete. The desired result is obtained, but I'm still unable to understood how or why things work. Understanding the process becomes something fundamental, especially if and when the code needs to be changed in the future. Below you can check the code of the whole project. To test, just run it.
My remaining questions are:
What are the differences between formats and static formats(domNode)? If you
observe code execution, they are called a few times, and static formats(domNode) are called twice. Is this normal? I do not know, that's why I am asking.
Within function highlightSelectedText(hl), why does hl appear with
a false value? How does this happen?
When formatting is applied, the create is used. When
it is removed, format(format, value) is called. There are snippets of code (inside format) that are never reached. Shouldn't applying
and removing formats be a format-only job? Why do I have to change
the DOM element in two different locations?
I think I can find the answer to number 2 by looking at the toolbar module source code. But I'm not quite sure. If I can figure it all out, I'll be back here to edit this answer, okay? I know others may also have the same questions.
Can you tell me what I am missing in writing this code?
<button onclick="getBrowserName()">You Browser Name?</button>
<script>
function getBrowserName()
{
//Uses external interface to reach out to browser and grab browser useragent info.
var browserAgent:String = ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
//Determines brand of browser using a find index. If not found indexOf returns (-1).
if(browserAgent != null && browserAgent.indexOf("Firefox")>= 0)
{
alert("Firefox");
}
else if(browserAgent != null && browserAgent.indexOf("Safari")>= 0)
{
alert("Safari");
}
else if(browserAgent != null && browserAgent.indexOf("MSIE")>= 0)
{
alert("IE");
}
else if(browserAgent != null && browserAgent.indexOf("Opera")>= 0)
{
alert("Opera");
}
else
{
alert("Undefined");
}
return 0;
}
</script>
Well, there are a few things wrong here.
var browserAgent: String: it appears that you're using actionscript syntax, but JS uses dynamic typing, so var is all you need. There's no need to explicitly define the variable's data type, and if you try to do it this way in JS, it's going to give you syntax errors.
ExternalInterface.call: this is another carryover from ActionScript: you don't need this. In fact, it won't work at all because there's no ExternalInterface class in standard JS.
Your getBrowser() function is unnecessary. You're setting browserAgent equal to the result of calling a function from an ExternalInterface, but you can do this directly: var browserAgent = window.navigator.userAgent.
When I fixed those things, it worked fine.
Next time, I would recommend checking the browser console, because, if nothing is happening, the errors that appear there will help you solve your issue nine times out of ten.
Demo
If you replace this line
var browserAgent:String = ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
with this line:
var browserAgent = window.navigator.userAgent;
Then your script works fine on my side.
However, the criteria you use to test the engine are not precise. Have a look at this:
http://www.useragentstring.com/pages/useragentstring.php
There are many browsers that will tell you Firefox even if they another brand. But they are based on each other or they use a specific engine that is built in other browsers too.
If I use your script with a Chrome browser, it says "Safari" instead of "undefined".
About the punctuation: I know of only two places in Javascript where to use the double point:
the conditional operator a = b ? c : d;
the attribute - value assignment in object notation: { name : value }
Your code line containing :String = ExternalInterface... reminds me rather on ActionScript (?).
Im not quite sure what the follow code should be doing. Are you sure its correct?
var browserAgent:String =
ExternalInterface.call("function getBrowser(){return navigator.userAgent;}");
I would expect this code to simply look like this:
var browserAgent = navigator.userAgent;
Below is a example with this change.
http://jsbin.com/lukasere/1/edit
I'm using the following code to compare returned IP address (Using node-restify which is similar to express):
var checkIP = function (config, req) {
var ip = req.connection.remoteAddress.split('.'),
curIP,
b,
block = [];
for (var i=0, z=config.ips.length-1; i<=z; i++) {
curIP = config.ips[i].split('.');
b = 0;
// Compare each block
while (b<=3) {
(curIP[b]===ip[b] || curIP[b]==='*') ? block[b] = true : block[b] = false;
b++;
}
// Check all blocks
if (block[0] && block[1] && block[2] && block[3]) {
return true;
}
}
return false;
};
config.ips contains an array which (as should be obvious from the code) can be specific or wildcarded IPs.
This works, but it seems like there is a more efficient way to do this. Just curious if anyone has any suggestions on a way to simplify this or make it more efficient. My request time nearly doubled when I introduced this and I'd like to squeeze out some load time if possible.
If my intuition is correct, you might be doing a bunch of extra work right now:
For each IP expression in your config.ips array, your code is parsing and comparing:
if (block[0] && block[1] && block[2] && block[3]) {
return true;
}
^^^ Note that you have already done work to get all 4 blocks in the iterations of calculating this expression 4 times per IP: (curIP[b]===ip[b] || curIP[b]==='*'), so the ANDing above is not preventing the overhead of the work that is already happening regardless.
I have 2 ideas for you:
Since IP addresses are strings anyways, the * notation lends itself to be suitable for a Regex to do the work, instead of your splitting and comparing? So maybe as a next step you could look into implementing a Regex to do the work, instead of .split() and compare, and test the performance of that?
Or maybe figure out how to avoid that overhead associated with comparing the parts all the time, and compare the wholes when you can? And then fall back into comparing the parts only when necessity requires it.
If you want to read some C code, here's how Apache does IP blacklisting behind the scenes. Look at the function named in_domain for some inspiration.
Good luck, hope this helps!
So, I'm writing a new JavaScript algorithm, codenamed jBam (Javascript Browser Algorithm Module), and it is used to write an extremely simple way to detect a browser and it's version. My main goal, is to be able to write this (with the library plugged in):
if(IE & version lessThan 9) {
// IE code for 8 and below
}
Right now, progress is going pretty good. I can write
if(browser == IE & version < 9)
So, back to my original question, the only thing I want to know is maybe someway set a variable for lessThan and set it equal to the symbol <. I've tried probably the most common remedy (var lessThan = <) but, of course, that's a little too wishful, and a little too easy. And, as we all know, nothing is ever too easy. Any suggestions, maybe a JavaScript preserve I'm not thinking of?
I agree with #JCOC611, but it's also possible that you make everything part of an object, and then make version an object as a property as well, where you can add a method called lessThan, so you would have something like:
var browserStuff = {
browser: {
value: "",
is: function (comparator) {
if (browserStuff.browser.value == comparator) {
return true;
}
return false;
}
},
version: {
value: "",
lessThan: function (comparator) {
if (browserStuff.version.value < comparator) {
return true;
}
return false;
}
},
init: function () {
// Do your stuff to determine the browser and its version
// You need to set browserStuff.version.value and browserStuff.browser.value
delete browserStuff.init;
}
}.init();
And then use it like:
if (browserStuff.browser.is("IE")) {
}
or
if (browserStuff.version.lessThan("7")) {
}
You'd have to add more things for "greater than" and "not equals" and whatnot, but that's just to start it off.
I don't exactly suggest this, because you can just as easily use normal Javascript operators to accomplish the same thing with less redundant code. This solution is more for a "readable" theme, but is in no way necessary or better.
how do I select special attributes like 'user:name' or 'city:id' using jQuery?
<div user:name="Ropstah"></div>
<span city:id="4"></div>
Javascript
//this works:
alert($('div').attr("user:name")); // alerts 'Ropstah'
//this doesn't work:
alert($('div[user:name]').attr("user:name")); //error
alert($('div[user\\:name]').attr("user:name")); //error even with special character escaping...
This is a bug in jQuery.
You have two options:
Get rid of the : and using "non standard" attributes (honestly, it's not a big deal)
Get more verbose, or use a plugin to get the functionality anyways:
Initially, you might have to do this:
$('div').filter(function() {
return $(this).attr('user:name') !== undefined;
}).whateverElse();
Speed wise this should be fairly close to jQuery's [] selector as that's what it's doing inside the library anyways. It is, of course, more to type every time you want to find an element that has an attribute, so you could write a plugin for it, since jQuery is awesome and lets you do this sort of thing:
$.fn.hasattr = function(attr) {
return this.filter(function() {
return $(this).attr(attr) !== undefined;
});
};
Which would then let you do a much simpler:
$('div').hasattr('user:name').whateverElse();
Or if you wanted to check if the attribute was equal to something, the plugin might be:
$.fn.cmpattr = function(attr, value) {
return this.filter(function() {
return $(this).attr(attr) == value;
});
};
And you could then do:
$('div').cmpattr('user:name', 'Ropstah').whateverElse();
Fix it:
jQuery.expr.match.ATTR = /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+|\w+:\w+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/;
// ^^^^^^^^
EDIT: This is not an official fix, it appears to work quite well though.
I've had quite a few headaches caused by jQuery and its inner-workings... sometimes, I think, it's okay to get stuck in there and fix it yourself. :)
If you're OK with using non-standard properties (which doesn't effect the rendering of your markup in any way.. it's pretty harmless, really) you can do this:
<div nonstandard="harmless" />
$("div[nonstandard='harmless']")
I've used this approach a few times. It doesn't hurt to be pragmatic.