I'm trying to sync Ace editor with Websocket, but when i applyDelta (receive by websocket). The on("change") event is trigger. Is there a way to apply changes without triggering events ?
socket.onmessage = (data) {
const deltas = JSON.parse(data);
editor.applyDeltas(deltas);
}
editor.on("change", (delta) => {
// here i trigger ws event
});
You can simply ignore the events generated by your code
var myChange = false
socket.onmessage = (data) {
const deltas = JSON.parse(data);
myChange = true
editor.applyDeltas(deltas);
myChange = false
}
editor.on("change", (delta) => {
if (myChange) return
// here i trigger ws event
});
Related
I have successfully used await browser.tabs.sendMessage in chrome to get response from the listener, but the same code in firefox does not work. await browser.tabs.sendMessage return immediately and sets response to undefined. In content script inject.js, sendResponse should be called after 1000ms timeout.
I attached a minimalistic example. Any idea why await browser.tabs.sendMessage
returns what sendResponse set only in chrome, but not in firefox?
//inject.js
(async () => {
if (typeof browser === "undefined") {
var browser = chrome;
}
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log(msg);
setTimeout(function(){
let pageObject = {a:1};
sendResponse(pageObject);
},1000)
return true;
});
})();
//background.js
(async () => {
if (typeof browser === "undefined") {
var browser = chrome;
}
//**code for injecting content scripts on extension reload**
browser.runtime.onInstalled.addListener(async () => {
let manifest = browser.runtime.getManifest();
for (const cs of manifest.content_scripts) {
for (const tab of await browser.tabs.query({ url: cs.matches })) {
browser.scripting.executeScript({
target: { tabId: tab.id },
files: cs.js,
});
}
}
});
async function SendMessageToFront(message) {
let resolve;
const promise = new Promise(r => resolve = r);
browser.tabs.query({}, async function (tabs) {
for (let index = 0; index < tabs.length; index++) {
const tab = tabs[index];
if (tab.url) {
let url = new URL(tab.url)
if (url.hostname.includes("tragetdomain.com")) {
var startTime = performance.now()
let response = await browser.tabs.sendMessage(tab.id, { message: message });
var endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`) // this takes 0ms
console.log("got response");
console.log(response); // this is undefined
console.log(browser.runtime.lastError); // this is empty
resolve(response);
break;
}
}
}
});
return promise;
}
await SendMessageToFront();
})();
I guess for the tests in firefox you do the reload of the background script (F5 or the specific button in devtools)
Just as you have coded the background you have little hope of getting an answer because every time you reload the background you break the wire with all content scripts injected into the page(s).
Move the browser check inside the "SendMessageToFront" function. Move the "SendMessageToFront" function (async is not needed) to the main thread and run that function in the main thread.
/*async*/ function SendMessageToFront(message) {
if (typeof browser === "undefined")
var browser = chrome;
let resolve;
const promise = new Promise(r => resolve = r);
browser.tabs.query({}, async function(tabs) {
for (let index = 0; index < tabs.length; index++) {
const tab = tabs[index];
if (tab.url) {
let url = new URL(tab.url);
if (url.hostname.includes("tragetdomain.com")) {
var startTime = performance.now()
let response = await browser.tabs.sendMessage(tab.id, {'message': message});
var endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`) // this takes 0ms
console.log("got response");
console.log(response); // this is undefined
console.log(browser.runtime.lastError); // this is empty
resolve(response);
break
}
}
}
});
return promise
}
(async _ => {
await SendMessageToFront()
})();
in this way you will get an error message as soon as the background is ready which tells you that the content script on the other side does not exists or it's not ready yet, but now, when the content script will be ready, you should just re-launch the function from the background script devtools
(async _ => {
await SendMessageToFront()
})();
this time you will get the correct answer {a: 1}
I am making an API call through an Observable. If this API call takes more than 200ms, I would like to show a loading screen (by assigning 'true' to my 'loading' variable), otherwise I don't want to show anything, in order to avoid a blink on screen.
Is there an RxJS operator capable of doing this ?
this.apiService.get(`/api/someEndpoint`)
// I hope for something like
.triggerIfAtLeastThisAmountOfTimeHasElapsed(200, () => {
this.loading = true;
})
.subscribe(response => {
// Process the response
this.loading = false;
});
There are many ways to do this so you can use for example this:
const api = this.apiService.get(`/api/someEndpoint`);
const loading = Observable
.timer(1000)
.do(() => loading = true) // show loading
.ignoreElements(); // or `filter(() => false)
Observable.merge(api, loading)
.take(1)
.subscribe(() => loading = false);
Along the same lines of Martin's response, this is an example that should simulate your context
const obs1 = Observable.timer(200).take(1);
const apiSubject = new Subject<string>();
const apiObs = apiSubject.asObservable();
const apiExecutionElapsed = 1000;
const obs3 = Observable.merge(obs1, apiObs);
let loading = undefined;
obs3.subscribe(
data => {
console.log(data);
if (loading === undefined && data === 0) {
loading = true;
} else {
loading = false;
}
console.log('loading', loading);
},
console.error,
() => {
loading = false;
console.log('loading', loading);
}
)
setTimeout(() => {
apiSubject.next('I am the result of the API');
apiSubject.complete()}, apiExecutionElapsed)
If the execution of the api (apiExecutionElapsed) takes longer than the configured timer (200 ms in this case) you see the loading flag to become first true and then false. Otherwise it remains always false.
Node walk presents an API with a few events like this.
walker.on('file', (root, filestats, next) => {
// next should be called to goto next file
next();
});
walker.on('end', () => {
// the end of the stream
});
Is it reactive if from the subscriber you're calling a function to inform the source to go to the next item in the stream ? Events don't wait for the subscriber to react to it, right ?
How do I transform this into a Rx Observable ?
Your best bet is to create a wrapper around it:
Rx.Observable.fromWalk = function(root, options, scheduler) {
scheduler = scheduler || Rx.Scheduler.currentThread;
return Rx.Observable.create(function(observer) {
var walker = walk.walk(root, options);
function fileHandler(x) {
observer.onNext({stats : x.stats, root : x.root});
scheduler.scheduleWithState(x, function(s, i) {
i.next();
});
}
var files = Rx.Observable.fromEvent(walker, 'file',
function(arr) {
return { root : arr[0], stats : arr[1], next : arr[2] };
});
var ended = Rx.Observable.fromEvent(walker, 'end');
return new Rx.CompositeDisposable(
files.subscribe(fileHandler),
ended.subscribe(observer.onCompleted.bind(observer))
);
});
};
I updated your example accordingly
Try Rx.Observable.fromCallback:
var walkerOn = Rx.Observable.fromCallback(walker.on, walker)
var source = walkerOn('file');
var subscription = source.subscribe(observer);
I've created extension that makes some JSON request & send it to some receiver.
My problem is:
Open popup window
After it closing, extensions sends 1 request
Open it on the same page again, and extension will send 2 requests
Open again, 4 requests
Open again, 8 requests
In each uses of popup, extension will be duplicate outgoing data in geometric progression.
Why that happens?
From the panel I'm send addnewurl to the port:
AddDialog.port.on("addnewurl", function (data) {
{
AddDialog is my popup
here It handle port messages aftre popup is closed(hidded)
}
var http = require("sdk/request").Request;
var req = CreateRequest("add_url", {});
req.params = {...};
var sreq = encodeURIComponent(JSON.stringify(req));
count += 1; //Global counter, u will see it in video
console.log('count = '+count);
var cfg = {
url : getRequestURL(),
contentType : "text/html",
content : sreq,
onComplete : function (response) {
var data = {
code : response.status,
body : response.json
};
AddDialog.port.emit("addnewurldone", data);
}
};
http(cfg).post();
});
For more sense I've created a AVI video record of that. See it here:
https://dl.dropboxusercontent.com/u/86175609/Project002.avi
1.6 MB
How to resolve that?
ADDED by request more info
That function emit addnewurl:
function AddNewURL() {
var node = $("#Tree").dynatree("getActiveNode");
if (node == null) {
$("#ServerStatus").text(LocalizedStr.Status_NoGroupSelected);
$("#ServerStatus").css("color", "red");
return;
};
var nkey = node.data.key;
var aImg = null;
var data = {
ownerId : nkey,
name : $("#LinkTitle").val(),
description : $("#LinkDesc").val(),
url : $("#CurrentURL").val(),
scrcapt:$("#ScrCaptureCB :selected").val()
};
$("#load").css("display", "inline");
$("#ServerStatus").text(LocalizedStr.Status_AddURL);
self.port.emit("addnewurl", data);
};
and it calls by button:
self.port.on("showme", function onShow(data) {
....
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
...
});
"swomme" goes from here(main.js):
AddDialog.on("show", function () {
count = 0;
AddDialog.port.emit("showme", locTbl);
});
function addToolbarButton() {
var enumerator = mediator.getEnumerator("navigator:browser");
while (enumerator.hasMoreElements()) {
var document = enumerator.getNext().document;
var navBar = document.getElementById('nav-bar');
if (!navBar) {
return;
}
var btn = document.createElement('toolbarbutton');
btn.setAttribute('id', cBtnId);
btn.setAttribute('type', 'button');
btn.setAttribute('class', 'FLAToolButton');
btn.setAttribute('image', data.url('icons/Add.png'));
btn.setAttribute('orient', 'horizontal');
btn.setAttribute('label', loc("Main_ContextMenu"));
btn.addEventListener('click', function () {
AddDialog.show();
}, false)
navBar.appendChild(btn);
}
}
I think the problem is here
document.querySelector('#BtnOk').addEventListener('click', function () {
AddNewURL();
});
If you are running AddDialog.port.emit("showme", locTbl); when you click your toolbar button then you're adding a click listener to #BtnOk every time as well.
On the first toolbar click it will have one click listener, on the second click two, and so on. You should remove the above code from that function and only run it once.
I was wondering if anyone has a working example of how to make a async ajax call while dragging an scheduler event. I have a horizontal grouping on different locations (resources in the scheduler) and want to update disabled timeslots on every location while using drag and drop. I have tried a few approaches with moveStart, but none of which made any progress.
Here is what I'm trying. The sync function is calling my API controller and should update the scheduler with the new disabled timeslots (slottemplate), but it never gets to the '$ajax'-part of the function (it's like it gets blocked).
moveStart: function (e) {
console.log(e.event);
selectedActivityId = e.event.activityId;
syncDisabledTimes(e.event.start);
syncDisabledTimes:
var asyncAjaxCalls = [];
for (var i = 0; i < selectedLocations.length; i++) {
asyncAjaxCalls.push(getAsyncCall(selectedLocations[i].locationId, e.event.start));
}
ajaxIsRdy = false;
$.when.apply($, asyncAjaxCalls).done(function () {
ajaxIsRdy = true;
var scheduler = data();
scheduler.view(scheduler.view().name);
});
getAsyncCall:
var asyncCall = $.ajax({
type: "GET",
url: "/api/v1/DisabledTimeTableUnits/",
data: {
'mode': DisabledTimeTableUnitsMode.OnlyDisabledTimes,
'filterOnOutsideTimeTableSet': true,
'filterOnSeasonLimits': true,
'filterOnOpeningHours': true,
'filterOnBookinglock': true,
'filterOnActivity': selectedActivityId === null ? false : true,
'utcTime': true,
'start': moment(_date).startOf('week').isoWeekday(1).toISOString(),
'end': moment(_date).endOf('week').isoWeekday(7).toISOString(),
'locationId': locationId,
'activityId': selectedActivityId
}
}).done(function (disabledTimeUnits) {
for (var i = 0; i < selectedLocations.length; i++) {
if (selectedLocations[i].locationId === locationId) {
selectedLocations[i].disabledTimeSlots = disabledTimeUnits;
}
}
});
return asyncCall;
MoveStart will fire once as you start dragging. Move will continuously fire as you drag the event around.
However, I would recommend that you perform your server side validation upon the MoveEnd event.