Chrome Extension Dev: Updating javascript variables in background.html - background

I have a working extension, and now I'm trying to add some options/features!
I have a background page which keeps track of the "state" of each tab (per tab javascript variables that hold options/settings). Now I want to have a popup.html file which will have 1 option, a time slider. I'm not concerned with the slider or any html/css. My issue is that I'm unsure of how to communicate the new setting to the background page.
My background page contains some code like this:
chrome.browserAction.onClicked.addListener(function(tab) {
Init(tab);
});
...
function Init(tab)
{
chrome.tabs.sendRequest(tab.id,
{
'TurnOffTimer': false,
'TimerIsOn': TimerIsOn,
'Interval': Interval,
'RefreshRate': RefreshRate
}, responseCallback);
}
TimerIsOn, Interval, and RefreshRate are javascript variables on the same page.
What I need to know is how, once the slider (which is declared in the popup.html) is set and the user clicks 'ok', the timer value can be sent to background.html to be stored in the appropriate javascript variable, and the extension functionality can be updated. I can create another function called Update which will take this updated value and run, but I need to know how I can call the Update function from popup.html if it's declared in background.html.
Hopefully I'm making sense but if clarification is necessary, feel free to ask.

You can directly access background page's window object from popup with chrome.extension.getBackgroundPage(). So to change some var in bg page from popup you can just call:
chrome.extension.getBackgroundPage().Interval = 5;
You would need to change your extension a bit, as once you have popup attached to browser action button, chrome.browserAction.onClicked listener won't be firing anymore. You would need to call Init from popup manually in a similar way:
chrome.extension.getBackgroundPage().Init();
And inside Init() manually get selected tab id, as it won't be passed anymore.

Related

Apply callback to all instances of Magnific Popup?

Is there a way to bind an event callback so that it's called for all instances of Magnific Popup? On a site I'm building several elements have fixed position and "jump" when the main browser scrollbar disappears. I want to set a callback to add padding for those elements before any popup is opened, and remove the padding when the popup is closed. I don't want to re-use the callbacks every time I initialize Magnific Popup – I'd rather set one set of callbacks that is run every time Magnific opens and closes a popup.
My first thought was to use event delegation and bind pretty high in the document tree. I was trying to bind to the mfpOpen event and listen on the BODY tag, but it doesn't seem like the event bubbles. Is there another solution?
Events are dispatched only on target element or on document (if target element is not present).
But you may override open function globally using prototype, e.g.:
$.magnificPopup.instance.open = function(data) {
console.log('before open anything');
$.magnificPopup.proto.open.call(this,data);
console.log('after open anything');
};
You may do the same thing with close function (it doesn't have any arguments).

Is it possible to HIDE Javascript Object's prototype! What's the MYSTERY behind this?

I'm using openui5. There is a constructor Function for UI control Button,unable to see the prototype properties of the Button but the same thing when executed in browser console, shows up!
sap.m.Button.prototype.Move = function(){
console.log('Move');
}
var oButton = new sap.m.Button({text:"Hello"});
oButton.Move(); // throws undefined function!
The same code when executed browser in console, it works!
jsbin --> http://jsbin.com/tepum/1/edit
After running the code I find that creating the first instance of sap.m.Button causes script to change the prototype of sap.m.Button. It's valid in JavaScript but not very smart if you ask me.
A first creation causes a synchronous request (no no as well) to fetch library-parameters.json.
If you run the code the second time it will have prototype.move because creating an instance of Button will not change the Button.prototype.
The capital M in Move would suggest a constructor function so I would advice changing it to lower case.
Since fetching the parameters is synchronous you can create the first instance and then set the prototype:
console.log("First Button creation changes Button.prototype");
var oButton = new sap.m.Button({text:"Hello"});
sap.m.Button.prototype.move = function(){
console.log('Move');
}
oButton.placeAt('content');
oButton.move(); // logs Move
My guess is that this is done to lazy load controls, if a Button is never created then the json config files are never loaded for these unused controls. It has a couple of drawbacks though.
You have to create an instance first before you can set the prototype.
The config files are synchronously loaded so when creating first instance of many controls with a slow connection would cause the app to be unresponsive.
A better way would be for a factory function to return a promise so you create the control the same way every time and the config files can be fetched asynchronously.
[update]
Looking at the config it seems to be config for the whole gui library so I can't see any reason why this is loaded only after creating a first instance. A library that changes it's object definitions when creating instances is not very easy to extend because it's unpredictable. If it only changes prototype on first creation then it should be fine but it looks like the makers of the library didn't want people to extend it or they would not make the object definition unpredictable. If there is an api documentation available then maybe try to check that.
[update]
It seems the "correct" way to extend controls is to use extend.
#HMR is right the correct way to extend a control is by using the extend function provided by UI5 managed objects, see http://jsbin.com/linob/1/edit
in the example below when debugging as mentoned by others you will notice that the control is lazy loaded when required, any changes you make prior are lost when loaded
jQuery.sap.declare("my.Button");
jQuery.sap.require("sap.m.Button");
sap.m.Button.extend("my.Button", {
renderer: {}
});
my.Button.prototype.Move = function() {
console.log('Move');
};
var oButton = new my.Button({
text: "Hello"
});
oButton.placeAt('content');
oButton.Move();
It's not hiding the prototype per se. If a constructor function exits normally then you get that function's prototype. But, if a constructor function actually returns some other object then you get that other object's prototype, so it's not valid to assume that just because you added to the Button prototype that when you call new Button() that you will see your method on whatever you get back. I'm sure if you de-obfuscate that code you'll find that the constructor you are calling has a "return new SomeOtherInstanceOfButton()" or similar at the end of it.
Edit: Ok it's a bit difficult to see what's really going on in that sap code but, it looks like they have code that overwrites the prototypes of controls to add features to them, such as: sap.ui.core.EnabledPropagator, and those things aren't run until you actually instantiate a button. So if you change your code to instantiate the button on the page, then add to it's prototype, then construct and call the method, it works fine. Like so:
http://jsbin.com/benajuko/2/edit
So I guess my answer is, when you run it from console it's finished mucking around with that prototype, whereas in your test you were adding to the prototype, then constructing the button for the first time (which changes the prototype again) then trying to call your old one, which is no longer there.

Win8 JS App: How can one prevent backward navigation? Can't set WinJS.Navigation.canGoBack

Fairly new to developing for Windows 8, I'm working on an app that has a rather flat model. I have looked and looked, but can't seem to find a clear answer on how to set a WinJS page to prevent backward navigation. I have tried digging into the API, but it doesn't say anything on the matter.
The code I'm attempting to use is
WinJS.Navigation.canGoBack = false;
No luck, it keeps complaining about the property being read only, however, there are no setter methods to change it.
Thanks ahead of time,
~Sean
canGoBack does only have a getter (defined in base.js), and it reflects the absence or presence of the backstack; namely nav.history.backstack.
The appearance of the button itself is controlled by the disabled attribute on the associated button DOM object, which in turn is part of a CSS selector controlling visibility. So if you do tinker with the display of the Back button yourself be aware that the navigation plumbing is doing the same.
Setting the backstack explicitly is possible; there's a sample the Navigation and Navigation History Sample that includes restoring a history as well as preventing navigation using beforenavigate, with the following code:
// in ready
WinJS.Navigation.addEventListener("beforenavigate", this.beforenavigate);
//
beforenavigate: function (eventObject) {
// This function gives you a chance to veto navigation. This demonstrates that capability
if (this.shouldPreventNavigation) {
WinJS.log && WinJS.log("Navigation to " + eventObject.detail.location + " was prevented", "sample", "status");
eventObject.preventDefault();
}
},
You can't change canGoBack, but you can disable the button to hide it and free the history stack.
// disabling and hiding backbutton
document.querySelector(".win-backbutton").disabled = true;
// freeing navigation stack
WinJS.Navigation.history.backStack = [];
This will prevent going backward and still allow going forward.
So lots of searching and attempting different methods of disabling the Back Button, finally found a decent solution. It has been adapted from another stackoverflow question.
Original algorithm: How to Get Element By Class in JavaScript?
MY SOLUTION
At the beginning of a fragment page, right as the page definition starts declaring the ready: function, I used an adapted version of the above algorithm and used the resulting element selection to set the disabled attribute.
// Retrieve Generated Back Button
var elems = document.getElementsByTagName('*'), i;
for (i in elems)
{
if((" "+elems[i].className+" ").indexOf("win-backbutton") > -1)
{
var d = elems[i];
}
}
// Disable the back button
d.setAttribute("disabled", "disabled");
The code gets all elements from the page's DOM and filters it for the generated back button. When the proper element is found, it is assigned to a variable and from there we can set the disabled property.
I couldn't find a lot of documentation on working around the default navigation in a WinJS Navigation app, so here are some methods that failed (for reference purposes):
Getting the element by class and setting | May have failed from doing it wrong, as I have little experience with HTML and javascript.
Using the above method, but setting the attribute within the for loop breaks the app and causes it to freeze for unknown reasons.
Setting the attribute in the default.js before the navigation is finished. | The javascript calls would fail to recognize either methods called or DOM elements, presumably due to initialization state of the page.
There were a few others, but I think there must be a better way to go about retrieving the element after a page loads. If anyone can enlighten me, I would be most grateful.
~Sean R.

What's the difference between .select, .show, and .load in jquery-ui tabs?

I see that there are three events fired when a tab is selected and I see the order in which the events are fired but I'm rather confused about which event to use or how these are truly different. If all three are fired, couldn't I just put my code in any of the events?
I must be missing something here. Can someone clarify?
So after messing with this more I'm going to share what I ended up doing in the hopes that it might help someone else.
I had to generate dynamic tabs based on data returned in an Ajax call. It's basically data by date where the tabs are the date and they display whatever data falls within that date.
Generating the tabs from the returned data was easy but I couldn't figure out how to write out the associated data. Finally (and I should have started here), I looked that the generated dom and noticed that a dynamically created tab also creates a div. Maybe that's obvious to some (it wasn't to me) and if it was in the documentation I missed it. Anyway, this code will generate tabs from an array and then append html to the associated div when the tab is clicked. I don't need all the variables but I thought that might make it more readable. Put the function for show before adding the tabs or it wont work!
var _sessionDates = getSessionDates(sessionData.Sessions);
var $tabs = $("#sub-tabs");
$tabs.tabs({
show: function(event, ui) {
var selected = $tabs.tabs('option', 'selected');
var _sessionDates = getSessionDates(sessionData.Sessions);
var grid = buildGrid(_sessionDates[selected]);
$('#' + _sessionDates[selected]).html(grid);
}
});
$(_sessionDates).each(function(i, dayOfShow) {
var d = dateFormat(dayOfShow, "mediumDate");
$tabs.tabs('add', '#' + dayOfShow, d);
});
Finally, I have to "scroll" through my data shown in the tab and I was able to do that with these two lines. The first line gives me the id of the div element corresponding to the selected tab (which is really the important part) and the second line just calls my method and passes in the id of the div less the '#'. My date is also my id. There's a global variable that I've changed outside of this that makes it work. I know that's bad and I'll remove it when I refactor it.
var $el = $($('#sub-tabs a')[$('#sub-tabs').tabs('option', 'selected')]).attr('href');
$($el).html(buildGrid($el.replace('#', '')));
select is triggered when a tab is clicked on, but before the tab has been shown
load is triggered after the contents of a remote tab have been loaded (i.e. a tab whose content is not part of the initial page payload, but that is loaded via an ajax call when the user clicks on the related tab)
show is triggered after a tab has been shown
The jQuery documentation makes this relatively clear, but I had to experiment in order to fully understand the difference between select and show.
This has been made more clear since the original question was asked so now the following is true:
Effectively, the functionality for "show" is now provided by "activate" and "select" is done with "beforeActivate" while "load" is still the same.
activate: (previously show) Triggered after a tab has been activated (after animation completes)
beforeActivate: (previously select) Triggered immediately before a tab is activated.
load: Triggered after a remote tab has been loaded.
Also provided are:
beforeLoad: Triggered when a remote tab is about to be loaded, after the beforeActivate event.
create: Triggered when the tabs are created.

Using Dojo to reenable a submit button when a form is edited

I'm trying to build a web app using Dojo. I have a form that posts data via Dojo's xhrPost capabilities to a server side program that saves changes made on the form whenever the user hits the "save" button. What I would like to do is disable the save button after a successful save until the next time something is changed in any of the form's fields to avoid repeated attempts to save an unchanged document.
I tried having Dojo's event watching functionality watch for changes, but have not been successful. The event intended to trigger reenabling the save button never does anything. Here is what I tried:
eventWatching.push(dojo.connect(dijit.byId('editForm'), 'onChange', function() { dijit.byId('saveButton').set('disabled', false); }));
Using onKeyPress instead of onChange seemed promising, but that did not (obviously) reenable the button when the form was edited using the mouse alone.
Prior to 1.6, I don't think dijit.form.Form connects its children's onChange to its own, which is likely why your onChange idea didn't work.
In Dojo 1.6, what you're asking for is easily possible by taking advantage of the fact that widgets now inherit dojo.Stateful functionality:
form.watch('value', function(property, oldvalue, newvalue) {
/* ... */
});
In 1.5 or lower this might take some work; can't think of an easy way off the top of my head but maybe someone else has an idea or one will hit me later.
You can find the code responsible for hooking up the onChange and value-watching functionality in 1.6 here: https://github.com/dojo/dijit/blob/master/form/_FormMixin.js#L396-429
If the newvalue is emptyString the form is valid.
frm1.watch('state',function(property, oldvalue, newvalue) {
console.log(newvalue)
})