Observable not working as expected with chrome.tab.query generated value - javascript

With the following code snipped in angular2. the url is working beginning given as aspected. If i run processTab without running it through chrome.tabs.query's asynchronous callback. it works perfectly but if i run it within the callback. the value is being passed to the processTab function but it is not working properly.
Not Working**
randomFunction() {
var self = this,
curl:string;
chrome.tabs.query({currentWindow: true, active: true}, function(tabs){
// self.updateUrl = tab.url.replace(/.*?:\/\//g, "")
curl = tabs[0].url.replace(/.*?:\/\//g, "").replace(/\/$/, "");
self.processTab(curl);
});
}
processTab(url:string) {
this.listService.getData(url)
.subscribe(
data => this.data = data,
error => this.errorMessage = <any>error);
console.log("the url: " + url);
}
Working:
randomFunction() {
this.processTab("www.whateverurl.com");
}
processTab(url:string) {
this.listService.getData(url)
.subscribe(
data => this.data = data,
error => this.errorMessage = <any>error);
console.log("the url: " + url);
}
but the value is being passed to processTab in both instances.

I assume chrome.tabs.query is not covered by zone.js. You need to make the code run explicitly inside Angulars zone for change detection detect model changes
class SomeClass {
constructor(private zone:NgZone) {}
randomFunction() {
curl:string;
chrome.tabs.query({currentWindow: true, active: true}, (tabs) => {
this.zone.run(() => {
// this.updateUrl = tab.url.replace(/.*?:\/\//g, "")
curl = tabs[0].url.replace(/.*?:\/\//g, "").replace(/\/$/, "");
this.processTab(curl);
});
});
...
}
}
also prefer arrow functions instead of self

Related

Autodesk Design Automation "Value cannot be null. (Parameter 'ForgeConfiguration.ClientId')"

I´ve downloaded the Forge Design Automation sample from the following link:
https://learnforge.autodesk.io/#/tutorials/modifymodels
But the downloable code example is not working fine. When any async method who involves the DesignAutomation API is called I get -> Value cannot be null. (Parameter 'ForgeConfiguration.ClientId'). So, I would like to know how it works and how I can set the ClientId in the ForgeConfiguration class or else if I making something else wrong. I attach a fragment of code where I get the error.
[HttpGet]
[Route("api/forge/designautomation/engines")]
public async Task<List<string>> GetAvailableEngines()
{
List<string> allEngines = new List<string>();
try
{
dynamic oauth = await OAuthController.GetInternalAsync();
// define Engines API
string paginationToken = null;
while (true)
{
Page<string> engines = await _designAutomation.GetEnginesAsync(paginationToken);
allEngines.AddRange(engines.Data);
if (engines.PaginationToken == null)
break;
paginationToken = engines.PaginationToken;
}
allEngines.Sort();
}
catch (Exception error) {
throw error;
}
return allEngines; // return list of engines
}
And the call of the method:
function prepareLists() {
list('engines', 'api/forge/designautomation/engines');
}
function list(control, endpoint) {
$('#' + control).find('option').remove().end();
jQuery.ajax({
url: endpoint,
success: function (list) {
if (list.length === 0)
$('#' + control).append($('<option>', { disabled: true, text: 'Nothing found' }));
else
list.forEach(function (item) { $('#' + control).append($('<option>', { value: item, text: item })); })
}
});
}
Did you forget to set the Forge App Keys in the Environment Variables of your project, check the page at https://learnforge.autodesk.io/#/environment/setup/netcore_da

How to write a singleton service for a Vue3 component javascript

I have a tooltip control I've written that works very nicely in Vue 3, but I need a mechanism to fire off to all other instances to tell them to close. There are delays on close, so I'm occasionally getting two tooltips to show up at the same time.
This method, which was a crutch I've used in the past, is not allowed by the compiler / build tools. I can full well understand why, but I don't know the right way:
tooltipManager: function() {
if (!window.TooltipManager) {
function tooltipManager() {
let _data = {
tooltipIndex: 0,
callbacks: {}
};
return {
register: function (callback) {
let id = "tooltip_" + _data.tooltipIndex;
_data.tooltipIndex++;
_data.callbacks[id] = callback;
return id;
},
closeOpenPopups: function (id) {
Object.keys(_data.callbacks).forEach(key => {
if (id !== key) {
_data.callbacks[key]();
}
});
},
destroy: function (id) {
delete _data.callbacks[id];
}
};
}
window.TooltipManager = tooltipManager();
}
return window.TooltipManager()
},
The first thing I tried but didn't work was a service which I imported:
export default class TooltipManager {
constructor() {
if(! TooltipManager.instance){
this._data = {
tooltipIndex: 0,
callbacks: {}
};
}
}
register (callback) {
let id = "tooltip_" + this._data.tooltipIndex;
this._data.tooltipIndex++;
this._data.callbacks[id] = callback;
return id;
}
closeOpenPopups(id) {
Object.keys(this._data.callbacks).forEach(key => {
if (id !== key) {
this._data.callbacks[key]();
}
});
}
destroy(id) {
delete this._data.callbacks[id];
}
}
Ok, I was close with the first service. It should be written this way, and I'm going to leave my console.logs in that confirmed that it is indeed a singleton even though it is running on different tooltips.
class TooltipManager {
constructor() {
if(! TooltipManager.instance){
this._data = {
tooltipIndex: 0,
callbacks: {}
};
console.log("got new instance");
} else {
console.log("got old instance");
}
}
register (callback) {
let id = "tooltip_" + this._data.tooltipIndex;
this._data.tooltipIndex++;
this._data.callbacks[id] = callback;
console.log("registered key: " + id);
return id;
}
closeOpenPopups(id) {
Object.keys(this._data.callbacks).forEach(key => {
if (id !== key) {
console.log("closed: " + key);
this._data.callbacks[key]();
}
});
}
destroy(id) {
delete this._data.callbacks[id];
}
}
export default new TooltipManager();
I got the following from console.logs:
got new instance
TooltipManager.js:19 registered key: tooltip_0
TooltipManager.js:19 registered key: tooltip_1
TooltipManager.js:19 registered key: tooltip_2
TooltipManager.js:19 registered key: tooltip_3
TooltipManager.js:26 closed: tooltip_0
TooltipManager.js:26 closed: tooltip_1
TooltipManager.js:26 closed: tooltip_2
TooltipManager.js:26 closed: tooltip_3
And indeed it solved the problem of ghost tooltips when one pops up before the other closes with a delay to prevent bounce.
In the tooltip tool I wrote, which I will later post here as an example of how easy Vue3 Teleport makes something like this to write. I want to test it a little longer before I show it off.
I just need to:
mounted() {
this.data.tooltipId = TooltipManager.register(this.forceHide);
And, which also shows some state data I use to keep track of this:
methods: {
forceHide: function() {
if (this.data.isDisplayed) {
this.data.style = '{top: -1000px, left: -1000px}';
}
this.data.hideRequested = false;
this.data.showRequested = false;
this.data.isDisplayed = false;
},
Now the next thing maybe using Vuex for this, but I may leave this in as an alternative method so it's not dependent on it.

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

Concurrent requests with axios

In a React app I need to post data from a form. The post creates a new dashboard object. Once that's done, I need to immediately update a select dropdown in the component to include the newly added dashboard name. The axios documentation says it should be done like so:
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// Both requests are now complete
}));
So this is what I've done:
class DashboardForm extends Component {
saveDashboard() {
var siteId = this.state.siteId;
var self= this;
return axios.post('/path/to/save/dashboard' + siteId + '/dashboards', {
slug: this.refs.dashboardUrl.value,
name: this.refs.dashboardName.value,
}).then(function (response) {
self.setState({
dashboardId: response.data.dashboardId,
dashboardName: response.data.dashboardName,
submitMessage: (<p>Successfully Created</p>)
});
self.setUrl(siteId, response.data.dashboardId);
}).catch(function (error) {
console.log(error);
self.setState({
submitMessage: (<p>Failed</p>)
});
});
}
getAllDashboards(){
var self = this;
self.setState({siteId: this.props.selectedSiteID});
var getDashboardsPath = "path/to/get/dashboards/" + self.props.selectedSiteID + "/dashboards";
axios(getDashboardsPath, {
credentials: 'include',
method: 'GET',
cache: 'no-cache'
}).then(function (response) {
return response.data.dashboards;
}).then(function (arrDashboards) { //populate options for the select
var options = [];
for (var i = 0; i < arrDashboards.length; i++) {
var option = arrDashboards[i];
options.push(
(<option className="dashboardOptions" key={option.dashboardId} value={option.slug}>{option.name}</option>)
);
}
self.setState({
options: options
});
});
}
saveAndRepopulate() {
axios.all([saveDashboard(), getAllDashboards()])
.then(axios.spread(function (savedDashboard, listOfDashboards) {
// Both requests are now complete
}));
}
}
The saveAndRepopulate is called when the form submits.
The problem is that I get the following errors:
error 'saveDashboard' is not defined no-undef
error 'getAllDashboards' is not defined no-undef
I've tried doing
function saveDashboard() {
but then I get
Syntax error: Unexpected token, expected ( (175:13)
|
| function saveDashboard() {
| ^
How do I call these two functions? Also, am I going to need to change the promise (.then) from the individual calls to the saveAndPopulate?
Many thanks for any guidance.
First, as #Jaromanda X pointed out, you should call your inside components functions with this, and you need to bind these functions to this. There are multiple ways to do that, one of then is to bind it inside the component constructor like:
this.saveDashboard = this.saveDashboard.bind(this)
Other good thing to do is to return the axios call inside the saveDashboard() and getAllDashboards()

Altering HTTP Responses in Firefox Extension

How can I alter the HTTP response body in a Firefox extension? I have setup an http-on-examine-response observer and an nsIStreamListener object with the code below. After I get the data, parse it, and alter it, how do I push the altered response back to the firefox browser? For example, let's say I go to Google.com with my extension enabled, the extension should intercept the response and change every occurence of "google" to "goggle". So when the page is loaded, the user will see "goggle" everywhere.
function TmSteroidsObserver()
{
this.register();
}
TmSteroidsObserver.prototype = {
observe: function(subject, topic, data) {
if (topic == "http-on-examine-response") {
}
else if (topic == "http-on-modify-request") {
var channel = subject.QueryInterface(Components.interfaces.nsIChannel);
var listener = new StreamListener(channel);
}
},
register: function() {
var observerService = Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(listener, "http-on-modify-request", false);
observerService.addObserver(listener, "http-on-examine-response", false);
},
unregister: function() {
var observerService = Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "http-on-modify-request");
observerService.removeObserver(this, "http-on-examine-response");
},
QueryInterface : function(aIID) {
if (aIID.equals(Components.interfaces.nsISupports) ||
aIID.equals(Components.interfaces.nsIObserver))
return this;
throw Components.results.NS_NOINTERFACE;
}
}
function StreamListener(channel) {
channel.notificationCallbacks = listener;
channel.asyncOpen(listener, null);
}
StreamListener.prototype = {
mData: "",
mChannel: null,
// nsIStreamListener
onStartRequest: function (aRequest, aContext) {
this.mData = "";
},
onDataAvailable: function (aRequest, aContext, aStream, aSourceOffset, aLength) {
var scriptableInputStream =
Components.classes["#mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableInputStream.init(aStream);
this.mData += scriptableInputStream.read(aLength);
},
onStopRequest: function (aRequest, aContext, aStatus) {
if (Components.isSuccessCode(aStatus)) {
// request was successfull
this.mCallbackFunc(this.mData);
} else {
// request failed
this.mCallbackFunc(null);
}
this.mChannel = null;
},
// nsIChannelEventSink
onChannelRedirect: function (aOldChannel, aNewChannel, aFlags) {
// if redirecting, store the new channel
this.mChannel = aNewChannel;
},
// nsIInterfaceRequestor
getInterface: function (aIID) {
try {
return this.QueryInterface(aIID);
} catch (e) {
throw Components.results.NS_NOINTERFACE;
}
},
// nsIProgressEventSink (not implementing will cause annoying exceptions)
onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
onStatus : function (aRequest, aContext, aStatus, aStatusArg) { },
// nsIHttpEventSink (not implementing will cause annoying exceptions)
onRedirect : function (aOldChannel, aNewChannel) { },
// we are faking an XPCOM interface, so we need to implement QI
QueryInterface : function(aIID) {
if (aIID.equals(Components.interfaces.nsISupports) ||
aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
aIID.equals(Components.interfaces.nsIChannelEventSink) ||
aIID.equals(Components.interfaces.nsIProgressEventSink) ||
aIID.equals(Components.interfaces.nsIHttpEventSink) ||
aIID.equals(Components.interfaces.nsIStreamListener))
return this;
throw Components.results.NS_NOINTERFACE;
}
};
You can use nsITraceableChannel to intercept the response.
You should modify the data which is available to what you need and pass it to the innerListener's OnDataAvailable
Below links would help you understand this better.
http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
http://www.ashita.org/howto-xhr-listening-by-a-firefox-addon/
For future readers looking for a way to do this in Firefox Quantum, there is an API that lets you filter responses. Using the method for long documents mentioned here, I was able to reliably change what I needed in my (temporary) plugin's background.js like so:
browser.webRequest.onBeforeRequest.addListener(
function fixenator(details) {
let filter = browser.webRequest.filterResponseData(details.requestId);
let decoder = new TextDecoder("utf-8");
let encoder = new TextEncoder();
let str = '';
filter.ondata = event => {
str += decoder.decode(event.data, {stream: true});
};
filter.onstop = event => {
str = str.replace(/searchPattern/g, 'replace pattern');
filter.write(encoder.encode(str));
filter.close();
}
return {};
},
{
urls: ['https://example.com/path/to/url']
//, types: ['main_frame', 'script', 'sub_frame', 'xmlhttprequest', 'other'] // optional
}
, ['blocking']
);
The observer service just call your listeners. Firefox will receive the requests,call your listeners, and send responses. see Mozilla docs Creating HTTP POSTs.

Categories