I am trying to write an extension that will pop-in and pop-out a side bar type view to allow for quick management of our HR help center.
Anyways, I've taken some existing stuff and modified it to do what I want. I plan to modify it even more than what I currently have, however, I want to make sure the functionality is there before I go to my boss and say LOOK! SEE!
I've gotten the basic idea to work with the browser action icon in Chrome. No issues there. The main issue comes when I try to enable a hotkey that will also open the extension. It just will not work, I've smacked my head against my desk one too many times and I need some outside assistance.
Anyhow here is the manifest section that I have handling the hotkey.
Manifest
"commands": {
"toggle-feature": {
"suggested_key": {
"default": "Ctrl+Shift+0",
"mac": "Command+Shift+0"
},
"description": "Toggle the helpcenter screen",
"global": true
},
"_execute_browser_action": {
"suggested_key": {
"windows": "Ctrl+Shift+9",
"mac": "Command+Shift+9",
"chromeos": "Ctrl+Shift+9",
"linux": "Ctrl+Shift+9"
}
}
As you can see I got pretty desperate and added hot keys for every system on the network and tried it on them all.
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.sendMessage(tab.id, { action: "toggleSidebar" });
});
chrome.commands.onCommand.addListener(function(tabh) {
chrome.tabs.sendMessage(tabh.id, { action: "toggleSidebarhkey" });
});
The second line is probably totally incorrect, I've screwed with it so many times trying to pass the information to the extension. The first line correctly handles the working half of the extension.
content.js
var sidebarURL = "help center server";
var isSidebarOpen = false;
var sidebar = createSidebar();
/* Create a pop-out */
function createSidebar() {
var sb = document.createElement("iframe");
sb.id = "mySidebar";
sb.src = sidebarURL;
sb.style.cssText = ""
+ "border: 3px groove;\n"
+ "width: 50%;\n"
+ "height: 100%;\n"
+ "position: fixed;\n"
+ "top: 0px;\n"
+ "left: 0px;\n"
+ "z-index: 9999;\n"
+ "";
return sb;
}
/* Show/Hide the pop-out */
function toggleSidebar() {
if (isSidebarOpen) {
document.body.removeChild(sidebar);
} else {
document.body.appendChild(sidebar);
}
isSidebarOpen = !isSidebarOpen;
}
**HERE IS WHERE I RUN INTO TROUBLE**
I copied and pasted the above code below and added the identifier from the background.js screen. I left the rest, because it should just use the current values to decide if it wants to close it or open it.
/* Show / Hide the pop-out via hotkey */
function toggleSidebarhkey() {
if (isSidebarOpen) {
document.body.removeChild(sidebar);
} else {
document.body.appendChild(sidebar);
}
isSidebarOpen = !isSidebarOpen;
}
/* Listen for message from the background-page and toggle the SideBar */
chrome.runtime.onMessage.addListener(function(msg) {
if (msg.action && (msg.action == "toggleSidebar")) {
toggleSidebar();
}
});
**HOT KEY LISTENER**
Once again this part is probably wrong as hell, but I've messed with it so much to try to make it work I doubt any part of it is correct.
/* Listen for message from the background-page and toggle the SideBar via hotkey */
chrome.runtime.onMessage.addListener(function(msg) {
if (msg.action && (msg.action == "toggleSidebarhkey")) {
toggleSidebarhkey();
}
});
To be honest I'm totally stuck, I suppose that many of you are thinking, "take the idea as it is to the boss!" and "your boss won't know!" but I want the extra cool-factor of not having to hunt down the button and easy access to the HR stuff in the helpcenter portal. Once I get the basic functionality of "hey Look!!" then I will expand, maybe add a panel instead of the pop-out (like google keep or hangouts) who knows, but I need a proof of concept first, and I hate to leave things uncompleted in this fashion.
As the docs on chrome.command explain, the callback for onMessage listener gets the command as the parameter, not the Tab object.
So, inside the callback you need to figure out which tab is active:
chrome.commands.onCommand.addListener( function(command) {
if(command === "toggle-feature"){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { action: "toggleSidebarhkey" });
});
}
});
Related
I am running a script to show a notification within a menu with scroll, but I do not know how to detect if the device has orientation landscape to validate the script.
The call onClick="VerHayMas();" works perfectly, but if the user open the menu once, clicking on #boton-menu and with your device in portrait, after changing the orientation to landscape the script no longer meet the objective.
The script has its logic ONLY if the device is in landscape, which is
when the menu needs to show the notification.
So, is it possible that my script is only valid with (max-width:999px) and (orientation:landscape), ignoring the portrait...?
I am a beginner in JS, and I do not know how to do it, really.
Any idea?
Thanks in advance!
HTML & CSS
#mas-menu {display:none}
<div id="boton-menu" onClick="VerHayMas();">+</div>
Script:
var clicksVerHayMas = 0;
function VerHayMas() {
clicksVerHayMas = clicksVerHayMas + 1;
if (clicksVerHayMas == 1) {
document.getElementById('mas-menu').style.display = 'block';
window.setTimeout(function() {
$('#mas-menu').fadeOut('slow');
}, 4000);
}
};
EDIT:
I have tried with the following script, but it does not work. If the user makes a call to onClick="VerHayMas();" in portrait mode, the script is no longer running in landscape mode.
What am I doing wrong here?
const matchesMediaQuery = window.matchMedia('(max-width:999px) and (orientation:landscape)').matches;
if (matchesMediaQuery) {
var clicksVerHayMas = 0;
function VerHayMas() {
clicksVerHayMas = clicksVerHayMas +1;
if(clicksVerHayMas == 1){
document.getElementById('mas-menu').style.display = 'block';
window.setTimeout(function(){
$('#mas-menu').fadeOut('slow');
},4000);
}};
}
I'd keep it simple, if screen height is less than width, then the user is in landscape mode. You can grab the height and width from the global window object.
if (window.innerWidth > window.innerHeight) {
// The user is in landscape mode!
userInLanscapeFunc();
}
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/innerHeight
You can solve this using matchMedia:
const matchesMediaQuery = window.matchMedia('(max-width:999px) and (orientation:landscape)').matches;
if (matchesMediaQuery) {
// do something
}
Make sure to note the browser support in the MDN link.
EDIT TO PROVIDE CONTEXT:
Because the user may be moving around their screen, you will want to make this evaluation inside VerHayMas, each time it is run, to determine if the main body of the script should be executed:
var clicksVerHayMas = 0;
function VerHayMas() {
var isLandscapeAndMeetsSizeRequirements = window.matchMedia('(max-width:999px) and (orientation:landscape)').matches;
if (isLandscapeAndMeetsSizeRequirements) {
clicksVerHayMas = clicksVerHayMas + 1;
if (clicksVerHayMas == 1) {
document.getElementById('mas-menu').style.display = 'block';
window.setTimeout(function() {
$('#mas-menu').fadeOut('slow');
}, 4000);
}
}
};
So VerHayMas will be run on every click, but only if the screen meets the requirements as determined by the media query string will it execute the code inside the if block.
So I'm attempting to combine the working solutions given in two different threads, I found here :
1) Delay pop-up for 10 seconds, only pop up once
2) FancyBox - "Don't show this message again" button?
I want my Fancybox to open after x seconds and then automatically close after xx seconds. I got that working just great!
Then when I want to add the link into my modal box that sets a cookie when the user clicks on that, to not ever show the box again to them for x days, it doesn't seem to set the cookie to do that effectively.
This is what I've got so far :
<script type="text/javascript">
function openFancybox() {
setTimeout( function() {$('#testbox').trigger('click'); }, 15000);
}
function dontshow(){
$.fancybox.close(); // Use this line if I want the button to close fancybox.
$.cookie('visited', 'yes', { expires: 7300 }); // Set the cookie.
}
$(document).ready(function() {
var visited = $.cookie('visited');
if (visited == 'yes') {
return false;
} else {
openFancybox();
}
$("#testBox").fancybox({
'hideOnContentClick': false,
'hideOnOverlayClick': true,
'showCloseButton': true,
'overlayShow': true,
'overlayOpacity': 0.3,
}); // ready
setTimeout( function() {$.fancybox.close(); },22000); // additive, so 15secs + 7secs open time = 22 secs
});
</script>
Yes, I have the JQuery cookie script on my server.
I'm suspecting that calling openFancybox() twice might be the problem (I dunno much about Javascript) .. but when I try to stick the :
{
setTimeout( function() {$('#testbox').trigger('click'); }, 15000);
}
after this bit :
else {
openFancybox()
... and I then just get lost with how many { and ; or ) I may / may not need!
(have tested endless combinations! .. a bit like trying to find a black cat in the dark when I don't really know what I'm doing .. just know what I want it to do!)
The inline code for my modal FancyBox box is :
<!-- INLINE FANCYBOX-->
<a id="testbox" href="#target"></a>
<div style="display:none"><div id="target">Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br>
<br>
<a id="noShow" href="javascript:dontShow()">Don't show this message again</a>
<br>
</div></div>
My test page URL : http://www.wayofthewomb.com/timed_pop_up_TESTER.html
Thank you for any suggestions / advice / guidance!
So grateful for this amazing resource, here!
You've nearly got it, but there are a couple of problems:
most importantly, you're missing the jquery.cookie dependency! This plugin is superceded by js-cookie. Include it in your page like:
<script src='//cdnjs.cloudflare.com/ajax/libs/js-cookie/2.0.4/js.cookie.min.js'></script>
You've got some mismatched casing between your html and JS: note testbox vs testBox
Otherwise you're good to go!
Here's a working fiddle: http://jsfiddle.net/memeLab/pr1f1cys/8/
function openFancybox() {
setTimeout(function () {
$('#testbox').trigger('click');
}, 5000);
}
function dontShow() {
$.fancybox.close();
// Set the cookie.
Cookies('visited', 'yes', {
expires: 7300
});
}
$(document).ready(function () {
var visited = Cookies('visited');
if (visited == 'yes') {
console.log('Theres a visited cookie');
return false;
} else {
openFancybox();
}
// create an event listener: when #noShow is clicked, run dontShow
$('#noShow').click(dontShow);
$('#testbox').fancybox({
'hideOnContentClick': false,
'hideOnOverlayClick': true,
'showCloseButton': true,
'overlayShow': true,
'overlayOpacity': 0.3
});
// additive, so 15secs + 7secs open time = 22 secs
setTimeout(function () {
$.fancybox.close();
}, 22000);
});
I suggest creating the most basic, reduced example using jsfiddle or codepen, etc when asking this kind of question: it simplifies the issue, and means that when your test page inevitably disappears, Those Who Come After can still see the code.
There are a couple of other issues in your page that are worth checking out:
looks like you're loading jquery more than once.. could be problematic!
you've nested an html comment <!-- --> inside a <style> tag, which is may be throwing off the syntax highlighting in your editor (doens't make life any easier!).
I suggest using a click handler on DocReady, rather than using the onClick html attribute (see #noShow)
Comment Syntax:
html comments: <!-- commented out -->
Javascript single line comments: // commented out
Javascript multiline / block comments: /* commented \n out */
CSS comments (may be multiline): /* commented out */
hope that helps!
I have set up an intro of a web page using the steps and setoptions functionality, and it works fine except when the user presses back.
The two issues I find are:
scrolltoelement works fine going forward, but the tooltip goes partly off screen when going backwards
the element selected in the first step is the entire page, so I use an "onafterchange" callback to reset the tooltip top and right offset. This works fine, except it appears to be ignored (or overwritten) when the back key is pressed
The javascript is:
var introProfile = introJs();
introProfile.setOptions({
tooltipPosition : 'top',
steps: [
{
element: '#intro_step1',
intro: 'Welcome to your example.com dashboard, where you can update your skills, your availability, and your professional details so that ...',
position: 'top'
}, {
element: '#intro_step2',
intro: 'Your profile contains important information which is important to complete so that...',
position: 'bottom'
},
{
element: '#intro_step3',
intro: 'Make sure your attribute is included in your profile because the client may express a preference.',
position: 'top'
},
{
element: '#intro_step4',
intro: 'Click here to add a photograph of yourself.',
position: 'top'
},
{
element: '#intro_step5',
intro: 'Your photograph will appear when your profile matches a ...',
position: 'top'
},
{
element: '#intro_step6',
intro: 'Take example.com with you, on your Android or Apple phone by clicking here.',
position: 'top'
}
]
});
introProfile.oncomplete(function() {
;
});
introProfile.onexit(function(){
;
});
introProfile.onchange(function(targetElement) {
; //add change bits here
});
introProfile.onafterchange(function(targetElement) {
console.log(targetElement.id);
switch (targetElement.id){
case "intro_step1":
$('.introjs-tooltip').css({top:'80px',left:'200px'});
}
});
introProfile.onbeforechange(function(targetElement) {
; // add change bits here
});
introProfile.start();
All I am doing in the HTML is setting the element id for intro_step1 to intro_step6
You can see the fiddle here: https://jsfiddle.net/brianlmerritt/3ocyuu65/10/
Any idea why "back" functionality is different from forward?
The problem was you wanted to change the position of the tooltip for the 1st step by using -
$('.introjs-tooltip').css({top:'80px',left:'200px'});
This was added in the "onafterchange" function -
introProfile.onafterchange(function(targetElement) {
console.log(targetElement.id);
switch (targetElement.id){
case "intro_step1":
$('.introjs-tooltip').css({top:'80px',left:'200px'});
}
});
Now this function was as expected called when you initialised the introjs - meaning after the position was changed by the introjs and then was overridden by your positions in the "onafterchange" function
But in case of when you hit back this function was called after the position was changed by introjs. So to fix this i used "setTimeout"
setTimeout(function(){
$('.introjs-tooltip').css({top:'80px',left:'200px'});
},600)
So now your positions are now overridden for the tooltip
Note: Your code would have worked if the poition changes for the tooltip was completed first and then the "onafterchange" function was called.
Fiddle: https://jsfiddle.net/kushal812/3ocyuu65/11/
Let me know if you find a better way!!
Really IntroJS is showing some errors when using the Back button. Here is the solution using Ionic with the Typescript Framework:
export class HomePage {
introApp = introJs.introJs(); //Declared the IntroJS
...
this.intro(); // Call the method
...
intro() { this.introApp.setOptions({...})} //Set the options in IntroJS
//Bug Correction
this.introApp.onafterchange(function(targetElement) {
console.log(targetElement.id);
switch (targetElement.id){
case "b1":
setTimeout(function(){
var element = document.getElementsByClassName('introjs-tooltip');
var boxArrow = document.getElementsByClassName('introjs-arrow top');
var numberLayer = document.getElementsByClassName('introjs-helperNumberLayer');
element.item(0).setAttribute("style", "top:210px;");
boxArrow.item(0).setAttribute("style", "display: block");
numberLayer.item(0).setAttribute("style", "left: 0; top:0;");
},600)
}
});
I'm trying to write a step that clicks a more button until that button changes to display: none;.
CLARIFICATION: The page I'm working with is a Google+ business page. There is a more button that loads about 10 reviews at a time until they are all loaded. Once they are, Google sets the button to display: none;
I've played around with a ton of different ways to accomplish it. Right now I'm working with something like:
This one is not working, and I'm sure there is an elegant way to accomplish this.
casper.then(function() {
while (this.visible('.d-s.L5.r0')){
console.log("Click");
this.click('.d-s.L5.r0');
}
});
This new code is working, but I have to set how many times it repeats which is pretty hacky:
casper.repeat(15, function() {
console.log("Click");
this.click('.d-s.L5.r0');
this.wait(400);
});
You need proper status handling of the button. By clicking the more button it will be invisible, but another loading span will be visible until the next items are loaded. Then it is exchanged again. You need to reflect the change in your code.
if (this.visible(moreButton)) { // synchronous
// asynchronous steps...
this.thenClick(moreButton);
this.waitUntilVisible(loadingButton);
this.waitUntilVisible(moreButton);
this.then(function(){
this.capture("business.png"); // overwrite the current screenshot
// recursion here
});
}
Additionally, it is good practice to write the thing recursive, because you don't know the number of steps until you try the steps. So the complete code would be:
var casper = require('casper').create({
waitTimeout: 10000,
viewportSize: {
width: 900,
height: 720
}
});
var moreButton = '[id*="about-page"] span.d-s.L5.r0',
loadingButton = moreButton + ' + span.dP.PA';
function clickMore(max, i){
i = i || 0;
if ((max == null || i < max) && this.visible(moreButton)) { // synchronous
// asynchronous steps...
this.thenClick(moreButton); // sometimes the click is not properly dispatched
this.waitUntilVisible(loadingButton);
this.waitUntilVisible(moreButton, null, function onTimeout(){
// only placeholder so that the script doesn't "die" here if the end is reached
});
this.then(function(){
this.capture("business_"+i+".png");
clickMore.call(this, max, i+1); // recursion
});
}
}
casper.start("https://plus.google.com/101200069268247335090/about?hl=en").then(function(){
clickMore.call(casper);
}).then(function(){
this.capture("business_end.png");
}).run(function(){
this.echo("DONE");
this.exit();
});
You can also call clickMore with an argument setting the maximum number of clicks like this:
clickMore.call(casper, 10);
A problem remains. The click on the more button is sometimes not dispatched. You should explore other approaches clicking that button.
I have a website that serves content like so, using .js:
var FluidNav = {
init: function() {
$("a[href*=#]").click(function(e) {
e.preventDefault();
if($(this).attr("href").split("#")[1]) {
FluidNav.goTo($(this).attr("href").split("#")[1]);
}
});
this.goTo("home");
},
goTo: function(page) {
var next_page = $("#"+page);
var nav_item = $('nav ul li a[href=#'+page+']');
$("nav ul li").removeClass("current");
nav_item.parent().addClass("current");
FluidNav.resizePage((next_page.height() + 40), true, function() {
$(".page").removeClass("current"); next_page.addClass("current");
});
$(".page").fadeOut(500);
next_page.fadeIn(500);
FluidNav.centerArrow(nav_item);
},
centerArrow: function(nav_item, animate) {
var left_margin = (nav_item.parent().position().left + nav_item.parent().width()) + 24 - (nav_item.parent().width() / 2);
if(animate != false) {
$("nav .arrow").animate({
left: left_margin - 8
}, 500, function() { $(this).show(); });
} else {
$("nav .arrow").css({ left: left_margin - 8 });
}
},
resizePage: function(size, animate, callback) {
if(size) { var new_size = size; } else { var new_size = $(".page.current").height() + 40; }
if(!callback) { callback = function(){}; }
if(animate) {
$("#pages").animate({ height: new_size }, 400, function() { callback.call(); });
} else {
$("#pages").css({ height: new_size });
}
}};
So far so good, but the client wants a browser back button functionality integrated. I did my homework and with the help of SO and other sources found out that the pushState and popState are the best way to achieve this.
If you take a second to read the code above, you will find out the following:
If you type website.com/#team in your browser, you are going to get the front page, not the #team page. If you want to navigate to #team, you are going to have to click from there.
If you navigate to #team and then want to go back to the front page, and you press the back button, you are going to get redirected to where you were before vising the website. Kicked out so to speak.
This is done with the purpose of a smoother browsing experience.
However, I now need to achieve the following, and I am a bit lost as to what changes I am going to have to make to my .js file above:
Achieve a working back-button functionality (manipulate the
browser's history storage)
When I type website.com/#team in the
URL bar, to go to that page and not the front page.
Here is an example website built with this functionality so you can see how it functions. http://themes.two2twelve.com/site/fluidapp/light/
You might try integrating a library that handles browsers incapable of pushState (fallback to hash urls like you're describing. I've had some luck with History.js in the past. (Get it??)
NOTE: This answer was provided by the asker as an edit to the question, rather than as an answer. I have moved it here to conform to site standards.
I did the following:
Download and unzip History.js to my server as instructed here: https://github.com/browserstate/history.js/
Follow the install here: https://github.com/browserstate/ajaxify
Installed the StateRouter from here Can't understand History.js, need it simplified? and here https://github.com/aknuds1/staterouter.js/tree/master/lib
So far what has been achieved:
Now the URLs actually change with the back button, meaning:
I go to Home -> Users -> Staff and then using the Back button the URL in the URL bar will change like so: www.123.com/#staff -> www.123.com/#users -> www.123.com/#home . However, only the URl changes and not the content. Any ideas what I am missing?