vendor prefix pseudo-selector crashes element.matches() Web API in Safari - safari

I'm building a utility into a style guide generator that automatically gathers the CSS for each element and displays it adjacent to the element in the output. The script I'm using to gather and parse the CSS is based on an answer from SO and uses the element.matches() web API.
Under most circumstances the code works perfectly, but in cases where there is a 'vendor prefix'-specific pseudo-element selector (e.g. ::-webkit-inner-spin-button as in Bootstrap 4.0), Safari throws an error at the most nested if statement (i.e. if (a.matches(rules[r].selectorText)) {):
SyntaxError (DOM Exception 12): The string did not match the expected pattern.
I've tried searching for this error specifically on SO and I found this question that talks about missing array endings, but I don't think the answer pertains to my issue.
I have a regex workaround that will remove the offending rules so the function can at least run, but as you can see, the properties in that rule ({background-color:black;}) are completely ignored in the output even though they're applied to the rendered element.
I could modify my regex to parse the strings and slice out problematic selectors while leaving the parsable rules, but overall this type of very specific hack feels inelegant to me, and I'm concerned it may cause problems down the road if my team ends up adding rules that rely on those types of vendor-prefixed pseudo-selectors.
Any ideas on how I can be a little more precise about working around (or solving) this issue?
Working Snippet
window.css = function (a) {
var sheets = document.styleSheets, o = [];
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
for (var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
if (a.matches(rules[r].selectorText)) {
o.push(rules[r].cssText);
}
}
}
return o;
}
$(document).ready(function(){
$('button').on('click',function(){
$('#styles').text(css( $( $('input').val() )[0] ));
});
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}
a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" value="#red"><button>Get styles</button>
<div>
<a id="red">#red anchor</a>
</div>
<aside id="styles"></aside>
Broken Snippet
(only change is 1 added line of CSS)
window.css = function (a) {
var sheets = document.styleSheets, o = [];
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
for (var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
if (a.matches(rules[r].selectorText)) {
o.push(rules[r].cssText);
}
}
}
return o;
}
$(document).ready(function(){
$('button').on('click',function(){
$('#styles').text(css( $( $('input').val() )[0] ));
});
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}
a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}
/* v ADDED THIS LINE - THIS IS THE ONLY CHANGE v */
[type="submit"]::-webkit-inner-spin-button {background-color:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" value="#red"><button>Get styles</button>
<div>
<a id="red">#red anchor</a>
</div>
<aside id="styles"></aside>
Reg-Ex Workaround
window.css = function (a) {
var sheets = document.styleSheets, o = [];
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
// v NEW FUNCTION v
function removeVendorPrefixSelectors (selectorText) {
if (/::-/.test(selectorText)) {
//do nothing
} else {
return selectorText;
}
}
for (var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
// v NEW LINE v
rule = removeVendorPrefixSelectors(rules[r].selectorText);
if (a.matches(rule)) {
o.push(rules[r].cssText);
}
}
}
return o;
}
$(document).ready(function(){
$('button').on('click',function(){
$('#styles').text(css( $( $('input').val() )[0] ));
});
});
div * {border-left:2px solid black;margin:1em 0;padding:.5em;}
a {text-decoration:none;display:block;cursor:pointer;}
#red {color:red;}
a, [type="submit"]::-webkit-inner-spin-button {background-color:black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" value="#red"><button>Get styles</button>
<div>
<a id="red">#red anchor</a>
</div>
<aside id="styles"></aside>

Related

Razor-Pages - HTML markup

Following the answer on this post I was using #:<div class="col-md-6">
Problems
The Formatting gets all messed up:
The intellicode gets all messed up (I only deleted the paragraph on the second #:)
Also, my if (i == (Model.CellsNotStarted.Count - 1) && i % 2 != 0) won't work when using the #:; it will always go on the else. If I don't use the #: markup, and if I, for instance put all my html in a if/else instead of only the <div> tag it will work with no problems.
My question is:
Is the #: markup viable? If so, what am I doing wrong. If not what is the alternative (that does not imply putting my entire code in an if/else)
PS: I know I should not post images of code, but I wanted to show the format and syntax errors.
There are neater ways to manage conditions within your Razor code. It's difficult to provide a full example because you don't provide the full code, but you can do this, for example:
<div class="col-md-#(i+1 == Model.CellsNotStarted.Count && i % 2 ! = 0 ? "12" : "6")">
Whenever you are tempted to use a remainder operator (%) in Razor, you should also consider whether a GroupBy would be better. It most often is, as I was shown in this question: Building tables with WebMatrix
You can try to use js to set html.Here is a demo:
<div id="myDiv"></div>
<script>
$(function () {
var html = "";
if ("#(Model.CellsNotStarted != null)"=="True")
{
for (var i = 0; i <#Model.CellsNotStarted.Count; i++)
{
if (i == (#Model.CellsNotStarted.Count - 1) && i % 2 != 0)
{
html += '<div class="col-md-12">';
}
else
{
html += '<div class="col-md-6">';
}
html += i+'</div>';
}
}
$("#myDiv").html(html);
})
</script>
result:

Using document.getElementsByClassName in Testcafe

I have a menu that always has the same structure, but the IDs can change from one installation to another. the only thing that stays the same is the heading (in my case "Plugins"). I call the document.getElementsByClassName function with a Selector inside my test:
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
Every heading of an menu element has the c-p-header-text class. Here is what a menu heading element looks like:
<div id="ext-comp-1002" class="c-p c-tree c-p-collapsed" style="width: auto;">
<div class="c-p-header c-unselectable c-accordion-hd" id="ext-gen135" style="cursor: pointer;">
<div class="c-tool c-tool-toggle" id="ext-gen140"> </div>
<img src="/backEnd/images/s.gif" class="c-p-inline-icon order"><span class="c-p-header-text" id="ext-gen150">Plugins</span>
</div>
It would be easy to use await t.click("#ext-gen150") but it is not safe that it is always this id.
here is what i tried:
await t
.click('#sites__db');
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
console.log("[DEBUG]" + slides);
console.log("[DEBUG] found " + slides.length + " elements");
for(var i = 0; i < slides.length; i++)
{
var txtOfCurrElem = slides.item(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
Running this test give me the following output:
[DEBUG]function __$$clientFunction$$() {
var testRun = builder.getBoundTestRun() || _testRunTracker2.default.resolveContextTestRun();
var callsite = (0, _getCallsite.getCallsiteForMethod)(builder.callsiteNames.execution);
var args = [];
// OPTIMIZATION: don't leak `arguments` object.
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}return builder._executeCommand(args, testRun, callsite);
}
[DEBUG] found 0 elements
The plan is to find the element (with the heading "Plugins") and then click on it when the test continuous.
You don't have to use document.getElementsByClassName in this case. You can just use CSS class selector instead:
var slides = Selector('.c-p-header-text');
You should use the count property when dealing with an array of Selectors. docs. Also, element properties, like exists, count, and DOM node state properties are Promisified, so when you use them not in t.expect, you should use the await keyword:
var count = await slides.length;
console.log("[DEBUG] found " + count + " elements");
for(var i = 0; i < count; i++)
{
var txtOfCurrElem = await slides.nth(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
I found a simple answer to my question. I use the .withText option to click on the Plugins element:
.click(Selector('span').withText("Plugins"))
Since this name is also unique, it is always the correct element that gets clicked. I do not know if it would have worked with the solution from #AndreyBelym if my site is not an extJS web application.

Can't print value from array of objects

Here I am filling the localStorage:
let storageObj = {};
storageObj.date = (new Date().toLocaleString().replace(", ","T"));
storageObj.url = this.responseURL;
localStorage.setItem(storageObj.date, storageObj.url);
In mounted() I am iterate all data from localStorage like:
for(let obj of Object.entries(localStorage))
{
this.lastpasts.push(obj);
}
And placing every Object to lastpasts (located in data() {lastpasts : []}).
In template I want to print only url:
<div class="ui divided items" v-for="el in lastpasts">
<div class="item">
{{el.url}}
</div>
</div>
But this code do not print nothing. Work only {{el}}. It's print in HTML block:
[ "24.06.2017T11:55:10", "362e9cc5-e7e6" ]
[ "24.06.2017T12:26:47", "b0f9f20d-7851" ]
Browser console do not have any errors.
In the chat session we managed to solve his issue.
It was caused by the fact, that he was using Array of Arrays instead of Array of Objects. Because Array can be used only with index, not field name, {{el.url}} was not working.
The code to get values from LocalStorage had to be changed to:
mounted() {
for(let obj of Object.entries(localStorage)) {
var x = {};
x.url = obj[0];
x.date = obj[1];
this.lastpasts.push(x);
}
}
Now, it is possible to use {{el.url}}

Is there a working sample of the Google custom search rest API?

I need to create a screen which automates Google search.
I know JavaScript and I'm trying to get GSE works.
I have a search engine and an API key.
The problem is Google's documentation is cyclic i.e. pages point to each other.
There is no working sample from where I can start my research.
Please help if you know of a working sample.
The documents I have read are:
cselement-devguide
introduction
I know this is an old question, but here is what I did to make the API results formatted like the Google Site Search used to give since they are ending the paid accounts and will have ads now. The API way has an option to pay still for over 100 searches per day, so going with that but had to format the results still, and used the existing one to build the css to do similar styling also.
Search form going to this page is just a simple:
<form action="search-results.htm" id="cse-search-box">
<div>
<input class="" name="q" type="text">
<input class="" type="submit">
</div>
</form>
and then the search results page:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>JSON/Atom Custom Search API Example</title>
<!--<link href="default.css" rel="stylesheet" type="text/css">-->
<link href="google.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="gsc-result-info" id="resInfo-0"></div>
<hr/>
<div id="googleContent"></div>
<script>
//Handler for response from google.
function hndlr(response) {
if (response.items == null) {
//Sometimes there is a strange thing with the results where it says there are 34 results/4 pages, but when you click through to 3 then there is only 30, so page 4 is invalid now.
//So if we get to the invalid one, send them back a page.
window.location.replace("searchresults.htm?start=" + (start - 10) + "&q=" + query);
return;
}
//Search results load time
document.getElementById("resInfo-0").innerHTML = "About " + response.searchInformation.formattedTotalResults + " results (" + response.searchInformation.formattedSearchTime + " seconds)";
//Clear the div first, CMS is inserting a space for some reason.
document.getElementById("googleContent").innerHTML = "";
//Loop through each item in search results
for (var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var content = "";
content += "<div class='gs-webResult gs-result'>" +
"<table class='gsc-table-result'><tbody><tr>";
//Thumbnail image
if (item.pagemap.cse_thumbnail != null)
content += "<td class='gsc-table-cell-thumbnail gsc-thumbnail'><div class='gs-image-box gs-web-image-box gs-web-image-box-portrait'><a class='gs-image' href='" + item.link + "'>" +
"<img class='gs-image' class = 'gs-image-box gs-web-image-box gs-web-image-box-portrait' src='" + item.pagemap.cse_thumbnail[0].src + "'></a></td>";
//Link
content += "<td><a class='gs-title' href='" + item.link + "'>" + item.htmlTitle + "</a><br/>";
//File format for PDF, etc.
if (item.fileFormat != null)
content += "<div class='gs-fileFormat'><span class='gs-fileFormat'>File Format: </span><span class='gs-fileFormatType'>" + item.fileFormat + "</span></div>";
//description text and URL text.
content += item.htmlSnippet.replace('<br>','') + "<br/><div class='gs-bidi-start-align gs-visibleUrl gs-visibleUrl-long' dir='ltr' style='word-break:break-all;'>" + item.htmlFormattedUrl +"</div>" +
"<br/></td></tr></tbody></table></div>";
document.getElementById("googleContent").innerHTML += content;
}
//Page Controls
var totalPages = Math.ceil(response.searchInformation.totalResults / 10);
console.log(totalPages);
var currentPage = Math.floor(start / 10 + 1);
console.log(currentPage);
var pageControls = "<div class='gsc-results'><div class='gsc-cursor-box gs-bidi-start-align' dir='ltr'><div class='gsc-cursor'>";
//Page change controls, 10 max.
for (var x = 1; x <= totalPages && x<=10; x++) {
pageControls += "<div class='gsc-cursor-page";
if (x === currentPage)
pageControls += " gsc-cursor-current-page";
var pageLinkStart = x * 10 - 9;
pageControls+="'><a href='search-results.htm?start="+pageLinkStart+"&q="+query+"'>"+x+"</a></div>";
}
pageControls += "</div></div></div>";
document.getElementById("googleContent").innerHTML += pageControls;
}
//Get search text from query string.
var query = document.URL.substr(document.URL.indexOf("q=") + 2);
var start = document.URL.substr(document.URL.indexOf("start=") + 6, 2);
if (start === "1&" || document.URL.indexOf("start=") === -1)
start = 1;
//Load the script src dynamically to load script with query to call.
// DOM: Create the script element
var jsElm = document.createElement("script");
// set the type attribute
jsElm.type = "application/javascript";
// make the script element load file
jsElm.src = "https://www.googleapis.com/customsearch/v1?key=yourApikeyhere&cx=yoursearchengineidhere&start="+start+"&q=" +query +"&callback=hndlr";
// finally insert the element to the body element in order to load the script
document.body.appendChild(jsElm);
</script>
</body>
</html>

Get the hostname with action script 2

I am creating some campaign swf banners and I don't use action script very often so any help from the experts would be great thanks.
I am supplying my banners on my website as resource downloads. And tutorials of how to embed the swf which has some javascript flashvars.
These flash variable is then concatenated into a google campaign link to change the utm_source.
This is my javascript...
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
<script type="text/javascript">
var flashvars = {};
flashvars.campaignSource = window.location.hostname;
var params = {};
params.loop = "true";
params.quality = "best";
params.wmode = "opaque";
params.swliveconnect = "true";
params.allowscriptaccess = "always";
var attributes = {};
swfobject.embedSWF("banner.swf", "banner_mpu", "300", "250", "9.0.0", false, flashvars, params, attributes);
</script>
and my html...
<div id="banner_mpu">
<a href="http://www.adobe.com/go/getflashplayer">
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
</a>
</div>
So the above js works great, however, not everyone will use my tutorial code and will probably use there own methods to embed the swf banner on their site.
So I need some back up action script 2 to get the current hostname into a action script variable
This is my action script which I have so far on my button (swf)...
on(release) {
function GetTheHostname() {
var RootFullUrl = _root._url;
txtFullUrl.text = RootFullUrl;
var lastSlashIndex:Number = RootFullUrl.lastIndexOf("/");
var DriveIndex:Number = RootFullUrl.indexOf("|");
if (DriveIndex>=0) {
baseUrl = RootFullUrl.substring(0, DriveIndex);
baseUrl += ":";
} else {
baseUrl = "";
}
baseUrl += RootFullUrl.substring(DriveIndex+1, lastSlashIndex+1);
txtBaseUrl.text = baseUrl;
return baseUrl;
}
var campaignSourceAS2:String= GetTheHostname();
if ( _root.campaignSource == undefined ) {
getURL("http://www.mysite.co.uk/?utm_source=" + campaignSourceAS2 + "&utm_medium=MPU&utm_campaign=My%20Campaign%202012", "_blank");
} else {
getURL("http://www.mysite.co.uk/?utm_source=" + _root.campaignSource + "&utm_medium=MPU&utm_campaign=My%20Campaign%202012", "_blank");
}
}
The problem with my action script is that it returns the full current URL.
Can any one please help me adapt the GetTheHostname function to get the host name instead of the baseURL
Thanks in advance
In that case, I guess it would be as easy as stripping the http:// from the url and then get all that's left to the first /
A one-liner to go from 'http://www.example.com/category/actionscript' to 'www.example.com' would be
var baseURL:String = _root._url.split("http://").join("").split("/")[0];
and to replace your full method
getURL("http://www.mysite.co.uk/?utm_source=" + (_root.campaignSource || _root._url.split("http://").join("").split("/")[0]) + "&utm_medium=MPU&utm_campaign=My%20Campaign%202012", "_blank");