Good day, I am new to JavaScript/TypeScript and looking for generic solution to indicate that ajax callback has completed so that other code that depends on the outcome of callback can execute.
For example class Fee Calculator, it gets instantiated with a service type and method calculate fee is called in order to display a fee of the service for the customer to agree.
class FeeCalculator {
private cost: number = 0;
private serviceType: number = 0;
private jsonAjax: JsonAjax = null;
constructor(serviceType: number) {
this.serviceType = serviceType;
this.jsonAjax = new JsonAjax("/tmaMaster/JSONServlet");
}
public calculateFee(): void {
// First, get the cost of the service from DB
this.cost = this.getServiceCost();
// calculate the fee and display it
// Now, this code would still be executed even if the callback
// didn't complete and therefore this.cost = 0,
// fee will be calculated wrongly
var fee: number = this.calculateTheFee(cost);
alert(fee);
}
getServiceCost = () => {
// IntegerWrapper is not relevant to this problem - just a primitive wrapper
var cost: IntegerWrapper = new IntegerWrapper();
this.execute("getServiceCost", this.serviceType, cost, this.setServiceCost);
}
// This is the callback
setServiceCost = (cost: IntegerWrapper) => {
this.cost = IntegerWrapper.primitiveValue;
}
private getFeeForTheCost(cost: number): number {
return cost / 4;
}
execute(command: string, requestDto: any, responseDto: any, callback: (responseDto: any) => void) {
var commandObject: JsonCommand = new JsonCommand(command, requestDto);
this.jsonAjax.call(commandObject, responseDto, callback);
}
}
Will also include JsonAjax class:
import JsonExternalizable = require("./JsonExternalizable");
class JsonAjax {
private READY_STATUS_CODE = 4;
private url: string;
constructor(url: string) {
this.url = url;
}
private isCompleted(request: XMLHttpRequest) {
return request.readyState === this.READY_STATUS_CODE;
}
call(requestDto: any, responseDto: any, callback: (responseDto: JsonExternalizable) => any) {
// Create a request
var request = new XMLHttpRequest();
// Attach an event listener
request.onreadystatechange = () => {
var completed = this.isCompleted(request);
if (completed && callback != null) {
/*
* N.B. PLease note that readFromJsonString returns void, therefore below line must be called sperately from line below.
*/
responseDto.readFromJsonString(request.responseText);
callback(responseDto);
}
};
// Specify the HTTP verb and URL
request.open('POST', this.url, true);
// Send the request
request.send(JSON.stringify(requestDto));
}
toString() {
return "JsonAjax";
}
}
export = JsonAjax;
The question is how do I make sure that calculateTheFee strictly called after getServiceCost is complete in other words make functions execute synchronously?
Ways I know of:
Do all other steps that require the cost value in a callback
itself. Not keen on that solution (what if there are more values
that need to be extracted from DB and all of them needed for
calculation) readability of that code will decrease.
Use timeout (Not generic enough, as we don't know how big is what we querying and how long it needs to "sleep")
P.S. Preferably without using extra libraries and plugins (company requirement)
Related
I am getting this error and I am not sure why. In my dialog class I have the Promse and the fail logic. I am getting this build error any help would be great.
Severity Code Description File Project Line Suppression State
Error TS2740 (TS) Type 'Promise' is missing the following properties from type 'JQueryPromise': state, always, done, fail, and 2 more.
var responsePromse = this.agentManager.getStyleGuideByAgentId(this.model.Id);
responsePromse.then((response) => {
this.styleguideNote.AgentType = response.responseObject.Name;
}).fail(() => {
this.relatedTemplates.loadingState = 0;
});
AgentManager
getStyleGuideByAgentId(Id: number): JQueryPromise<any> {
var request = this.agentsService.getStyleGuideByAgentId(Id);
console.log(request);
return request;
}
AgentService
getStyleGuideByAgentId(Id: number) {
var x = this.http.post(this.apiUrls.GetStyleGuideNotes, JSON.stringify(Id), { headers: ServiceBase.headers }).toPromise();
return x;
}
Conroller
[HttpPost]
public IHttpActionResult GetStyleGuideNoteById(long agentId)
{
var dbResult = StyleGuideNotesDataService.GetStyleGuideNoteById(agentId);
var styleguideNote = dbResult
.Select(x=> new
{
Section =x.Section,
AgentType = x.AgentType,
Status = x.Status.Name
})
.Distinct()
.ToList();
return Ok(styleguideNote);
}
TS Error message like this format Type 'A' is missing following properties from type 'B' means that, you typed 'B' explicitly to a value, but type of the value is actually 'A'.
so, You typed something explicitly as JQueryPromise, but that thing was actually Promise.
I think this code makes error.
getStyleGuideByAgentId(Id: number): JQueryPromise<any> {
var request = this.agentsService.getStyleGuideByAgentId(Id);
console.log(request);
return request;
}
I'm not sure but getStyleGuideByAgentId actually returns standard Promise, not JqueryPromise due to toPromise()
so, changing code like this might be solve your problem.
async getStyleGuideByAgentId(Id: number): Promise<any> {
var request = await this.agentsService.getStyleGuideByAgentId(Id);
console.log(request);
return request;
}
Here is what I am starting with. convert uses svgInjector to initiate and destroy a resource.
export async function convert(
serializedSvg: string,
svgSourceId: string,
containerId: string
): Promise<string> {
const svgInjector = new SvgInjector(serializedSvg, containerId).inject();
if (!svgInjector.injectedElement) {
throw new Error("Svg not injected");
}
const doc = new TargetDocument({});
const xml = convertRecursively(
svgInjector.injectedElement,
doc,
{
svgSourceId,
}
);
svgInjector.remove();
return doc.saveXML();
}
How can I rewrite this to instead have a higher order function initiate, provide and destroy the resource svgInjector.injectedElement to the convert function?
EDIT:
Here is a minimal reproducible example:
var svg = '<svg xmlns="http://www.w3.org/2000/svg"><text x="20" y="20">I am made available in DOM</text></svg>'
function convert(
serializedSvg,
containerId
) {
// make resource available (cross-cutting convern)
var container = document.getElementById(containerId);
var resource = new DOMParser().parseFromString(serializedSvg, "image/svg+xml").documentElement;
container.appendChild(resource);
// core convert functionality does things with resource
console.log(resource.getBBox())
// clean up resource (cross-cutting concern)
resource.remove()
}
convert(svg, "container")
<!DOCTYPE html>
<html>
<head>
<title>Minimal</title>
</head>
<body>
<div id="container">
</div>
</body>
</html>
EDIT 2
Here is a TypeScript version of the JavaScript in the previous edit
var svg = '<svg xmlns="http://www.w3.org/2000/svg"><text x="20" y="20">I am made available in DOM</text></svg>'
function convert(
serializedSvg: string,
containerId: string
) {
// make resource available (cross-cutting convern)
var container = document.getElementById(containerId);
if (!(container instanceof HTMLDivElement)) {
throw new Error("Extpected a div element")
}
var resource = new DOMParser().parseFromString(serializedSvg, "image/svg+xml").documentElement;
if (!(resource instanceof SVGSVGElement)) {
throw new Error("Extpected a svg element")
}
container.appendChild(resource);
// core convert functionality does things with resource
console.log(resource.getBBox())
// clean up resource (cross-cutting concern)
resource.remove()
}
convert(svg, "container")
I'm not sure if this is the sort of thing you're looking for exactly, but I'd be inclined to invert the control flow so that convert() uses or is passed a "resource manager" which takes care of the creation, furnishing, and deletion of the resource. A ResourceManager could simply be a function like:
type ResourceManager<T, I> = <R>(initProps: I, cb: (resource: T) => R) => R;
So a ResourceManager<T, I> is a function that takes some initial property bag of type I to specify which resource of type T is needed, and a callback function that does the actual work after the resource is available and before it is destroyed. If the callback function returns a result, then so does the resource manager.
This ResourceManager<T, I> is the generic contract that can be reused for different types of resource. Of course different types of resource need their own implementations. For example, I'd pull out of your convert() function a ResourceManager<SVGSVGElement, { serializedSvg: string, containerId: string }> like this:
const svgManager: ResourceManager<SVGSVGElement, { serializedSvg: string, containerId: string }> =
(initProps, cb) => {
// make resource available)
var container = document.getElementById(initProps.containerId);
if (!(container instanceof HTMLDivElement)) {
throw new Error("Extpected a div element");
}
var resource = new DOMParser().parseFromString(initProps.serializedSvg, "image/svg+xml").documentElement;
if (!(resource instanceof SVGSVGElement)) {
throw new Error("Extpected a svg element")
}
container.appendChild(resource);
// core functionality
const ret = cb(resource);
// clean up resource
resource.remove()
// return returned value if we have one
return ret;
}
Notice how the "core functionality" is just deferred to the callback, whose return value is held onto in case it is needed. Then convert() is simplified to:
function convert(
serializedSvg: string,
containerId: string
) {
svgManager({ serializedSvg, containerId }, (resource => console.log(resource.getBBox())));
}
Where resource => console.log(resource.getBBox()) is the function that does the work without caring about how to get or dispose of resource.
Hope that helps or gives you some ideas. Good luck!
Playground link to code
Here is my best attempt so far. I hope someone smarter posts a better solution.
Two weaknesses of the solution below I see are:
The typing of the enhancer is not generic, hindering reuse
The enhancer requires binding, hindering enhancer compose
type Props = {
svg: SVGSVGElement;
svgSourceId: string;
containerId: string;
};
async function convertBase(props: Props): Promise<string> {
const doc = new TargetDocument({});
const xml = convertRecursively(props.svg, doc, {
svgSourceId: props.svgSourceId,
});
return doc.saveXML();
}
type EnhancerProps = {
serializedSvg: string;
svgSourceId: string;
containerId: string;
};
type EnhancerPropsLight = {
svgSourceId: string;
containerId: string;
};
function enhancer(fn: Function, props: EnhancerProps) {
const rest = omit(["serializedSvg"])(props) as EnhancerPropsLight;
const svgInjector = new SvgInjector(
props.serializedSvg,
props.containerId
).inject();
if (!svgInjector.injectedElement) {
throw new Error("Svg not injected");
}
const res = convertToTgmlBase({ ...rest, svg: svgInjector.injectedElement });
svgInjector.remove();
return res;
}
const convert = enhancer.bind(null, convertBase);
export { convert };
Right now I have a queue (JS array) that is used to store players waiting for a game. I need the FIFO property of a queue so that players who were added to the queue first, get put in a new game first. The problem with a queue is that it doesnt have constant time lookup. It would be great if I could have a map that kept track of the order of insertion (i know that relying on a map to do this is JS is not reliable). If I give the property a value for its insertion order, it would need to be updated if someone leaves the queue, so that isnt helpful either. Anyway around this? A way to get constant lookup and maintain insertion order?
If you don't have memory constraints, maybe you can maintain a map with the queue implemented as a double linked list. Here is a sample implementation:
function Queue() {
var oldestRequest,
newestRequest,
map = {};
this.addUser = function(userID) {
var newRequest = { userID: userID };
map[userID] = newRequest;
// Set this as the oldest request if it is the first request
if (!oldestRequest) {
oldestRequest = newRequest;
}
// If this isn't the first request, add it to the end of the list
if (newestRequest) {
newestRequest.next = newRequest;
newRequest.previous = newestRequest;
}
newestRequest = newRequest;
};
this.nextUser = function() {
// If we don't have any requests, undefined is returned
if (oldestRequest) {
var request = oldestRequest;
oldestRequest = request.next;
delete map[request.userID];
// Make sure we don't hang on to references to users
// that are out of the queue
if (oldestRequest) {
delete oldestRequest.previous;
}
// This is the last request in the queue so "empty" it
if (request === newestRequest) {
newestRequest = undefined;
}
return request;
}
};
this.removeUser = function(userID) {
var request = map[userID];
delete map[userID];
if (request.previous) {
request.previous.next = request.next;
}
if (request.next) {
request.next.previous = request.previous;
}
};
return this;
}
You can use a map together with a queue to provide constant time access. Below is the implementation in TypeScript 4.2. Map is used instead of Object to provide better performance in addition and removal of values.
// TypeScript typing
export type KeyValuePair<K, V> = [ K, V ]
interface ValueData<V> {
value: V
refCount: number
}
// Public classes
export class MapQueue<K, V> {
readonly #queue: Array<KeyValuePair<K, V>>
readonly #map: Map<K, ValueData<V>>
constructor () {
this.#queue = []
this.#map = new Map()
}
get length (): number {
return this.#queue.length
}
unshiftOne (pair: KeyValuePair<K, V>): number {
const [key, value] = pair
const valueData = this.#map.get(key)
if (valueData !== undefined) {
if (valueData.value !== value) {
throw new Error(`Key ${String(key)} with different value already exists`)
}
valueData.refCount++
} else {
this.#map.set(key, {
value,
refCount: 1
})
}
return this.#queue.unshift(pair)
}
pop (): KeyValuePair<K, V> | undefined {
const result = this.#queue.pop()
if (result !== undefined) {
const valueData = this.#map.get(result[0])
if (valueData !== undefined) {
valueData.refCount--
if (valueData.refCount === 0) {
this.#map.delete(result[0])
}
}
}
return result
}
get (key: K): V | undefined {
return this.#map.get(key)?.value
}
}
I am very new to Vaadin and v-Leaflet. I have created a component that takes some geojson data and puts it over a map. I am able to click on any of the polygons/ multi polygons and get back a few bits of information in the form of a notification. The thing is, I need to take this information and click event and have it influence about 4 other separate components in their own separate classes. I have been racking my brain with this for the past 2 days and just can't seem to grasp it.
Here is my map/ click event:
private LMap map;
String filePath = this.getClass().getResource("/fccpeasgeo.json").getPath();
File file = new File(filePath);
//ArrayList<String> peaNames = new ArrayList<String>();
//#Override
public LMap createMap() {
map = new LMap();
FeatureJSON io = new FeatureJSON();
try {
long currentTimeMillis = System.currentTimeMillis();
// Look ma, no proxy needed, how cool is that!
FeatureCollection fc = io.readFeatureCollection(file);
Logger.getLogger(LeafletMap.class.getName()).severe("Download in " + (System.currentTimeMillis() - currentTimeMillis));
currentTimeMillis = System.currentTimeMillis();
FeatureIterator iterator = fc.features();
try {
while (iterator.hasNext()) {
Feature feature = iterator.next();
final String name = feature.getProperty("PEA_Name").getValue().toString();
final String population = feature.getProperty("POPs_2010").getValue().toString();
Geometry geometry = (Geometry) feature.getDefaultGeometryProperty().getValue();
// The geojson provided in example is rather complex (several megabytes)
// Use JTS to simplyfy. Note that it is rather easy to use
// different settings on different zoom levels, as well as decide
// to drop the feature form client altogether
geometry = DouglasPeuckerSimplifier.simplify(geometry, 0.2);
// In this example can be Polygon/Multipolygon
Collection<LeafletLayer> toLayers = JTSUtil.toLayers(geometry);
for (LeafletLayer l : toLayers) {
map.addComponent(l);
if (l instanceof LPolygon) {
LPolygon lPolygon = (LPolygon) l;
lPolygon.addClickListener(new LeafletClickListener() {
#Override
public void onClick(LeafletClickEvent event) {
Notification.show("PEA: " + name + " Population: " + population);
}
});
}
}
}
Logger.getLogger(LeafletMap.class.getName()).severe("Reducing and creating layers " + (System.currentTimeMillis() - currentTimeMillis));
} finally {
iterator.close();
}
} catch (MalformedURLException ex) {
Logger.getLogger(LeafletMap.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(LeafletMap.class.getName()).log(Level.SEVERE, null, ex);
}
map.zoomToContent();
//map.setCenter(40, -95.2);
//map.setZoomLevel(2.5);
LTileLayer tf = new LTileLayer();
tf.setUrl("http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png");
tf.setSubDomains(new String[]{"a", "b", "c"});
tf.setActive(true);
map.addBaseLayer(tf, "ThunderForest Transport");
return map;
}
Here is one of the components that will be receiving the event.
public Chart mhzPerSqMile() {
Chart chart = new Chart();
run();
chart.setCaption("Total MHz Per Square Mile");
chart.getConfiguration().setTitle("");
chart.getConfiguration().getChart().setType(ChartType.PIE);
chart.getConfiguration().getChart().setAnimation(false);
chart.setWidth("100%");
chart.setHeight("90%");
DataSeries series = new DataSeries();
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName()!=null)
{
if (evt.getPropertyName().equals("abcTask"))
{
}
}
for (int i = 0; i < 5; i++) {
Operator operator = operators.get(i);
if (selectedPea != null) {
if (operator.getPeaName().toLowerCase() == selectedPea.toLowerCase()){
DataSeriesItem item = new DataSeriesItem(operator.getName(),
operator.getTotalMHzSqMile());
series.add(item);
item.setColor(DummyDataGenerator.chartColors[i]);
}
}
}
chart.getConfiguration().setSeries(series);
PlotOptionsPie opts = new PlotOptionsPie();
opts.setBorderWidth(0);
opts.setShadow(false);
opts.setAnimation(false);
chart.getConfiguration().setPlotOptions(opts);
Credits c = new Credits("");
chart.getConfiguration().setCredits(c);
return chart;
}
}
}
Any advice would be very appreciated!
what I think you simply need to fire a property with any specific name (Event name) and all your listener classes have implemented PropertyChangeListener which triggers on every fireProperty() call , obviously you are matching your event name there and hence all you 4 classes perform their task upon receiving such a property change
You need to register PropertyChangeSupport for current instance
PropertyChangeSupport pcs = new PropertyChangeSupport(this);
further you'll use this object to fire your event
Firing event
//this firing code will probably go inside your click method that actually causes an event to occur
pcs.firePropertyChange("abcTask", oldValue, newValue);
Recieving event
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getPropertyName()!=null)
{
if (evt.getPropertyName().equals("abcTask"))
{
//perform task
}
}
}
I have a similar question here, but I thought I'd ask it a different way to cast a wider net. I haven't come across a workable solution yet (that I know of).
I'd like for XCode to issue a JavaScript command and get a return value back from an executeSql callback.
From the research that I've been reading, I can't issue a synchronous executeSql command. The closest I came was trying to Spin Lock until I got the callback. But that hasn't worked yet either. Maybe my spinning isn't giving the callback chance to come back (See code below).
Q: How can jQuery have an async=false argument when it comes to Ajax? Is there something different about XHR than there is about the executeSql command?
Here is my proof-of-concept so far: (Please don't laugh)
// First define any dom elements that are referenced more than once.
var dom = {};
dom.TestID = $('#TestID'); // <input id="TestID">
dom.msg = $('#msg'); // <div id="msg"></div>
window.dbo = openDatabase('POC','1.0','Proof-Of-Concept', 1024*1024); // 1MB
!function($, window, undefined) {
var Variables = {}; // Variables that are to be passed from one function to another.
Variables.Ready = new $.Deferred();
Variables.DropTableDeferred = new $.Deferred();
Variables.CreateTableDeferred = new $.Deferred();
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'drop table Test;',
[],
Variables.DropTableDeferred.resolve()
// ,WebSqlError
);
});
$.when(Variables.DropTableDeferred).done(function() {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'CREATE TABLE IF NOT EXISTS Test'
+ '(TestID Integer NOT NULL PRIMARY KEY'
+ ',TestSort Int'
+ ');',
[],
Variables.CreateTableDeferred.resolve(),
WebSqlError
);
});
});
$.when(Variables.CreateTableDeferred).done(function() {
for (var i=0;i < 10;i++) {
myFunction(i);
};
Variables.Ready.resolve();
function myFunction(i) {
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'INSERT INTO Test(TestID,TestSort) VALUES(?,?)',
[
i
,i+100000
]
,function() {}
,WebSqlError
)
});
};
});
$.when(Variables.Ready).done(function() {
$('#Save').removeAttr('disabled');
});
}(jQuery, window);
!function($, window, undefined) {
var Variables = {};
$(document).on('click','#Save',function() {
var local = {};
local.result = barcode.Scan(dom.TestID.val());
console.log(local.result);
});
var mySuccess = function(transaction, argument) {
var local = {};
for (local.i=0; local.i < argument.rows.length; local.i++) {
local.qry = argument.rows.item(local.i);
Variables.result = local.qry.TestSort;
}
Variables.Return = true;
};
var myError = function(transaction, argument) {
dom.msg.text(argument.message);
Variables.result = '';
Variables.Return = true;
}
var barcode = {};
barcode.Scan = function(argument) {
var local = {};
Variables.result = '';
Variables.Return = false;
window.dbo.transaction(function(myTrans) {
myTrans.executeSql(
'SELECT * FROM Test WHERE TestID=?'
,[argument]
,mySuccess
,myError
)
});
for (local.I = 0;local.I < 3; local.I++) { // Try a bunch of times.
if (Variables.Return) break; // Gets set in mySuccess and myError
SpinLock(250);
}
return Variables.result;
}
var SpinLock = function(milliseconds) {
var local = {};
local.StartTime = Date.now();
do {
} while (Date.now() < local.StartTime + milliseconds);
}
function WebSqlError(tx,result) {
if (dom.msg.text()) {
dom.msg.append('<br>');
}
dom.msg.append(result.message);
}
}(jQuery, window);
Is there something different about XHR than there is about the executeSql command?
Kind of.
How can jQuery have an async=false argument when it comes to Ajax?
Ajax, or rather XMLHttpRequest, isn't strictly limited to being asynchronous -- though, as the original acronym suggested, it is preferred.
jQuery.ajax()'s async option is tied to the boolean async argument of xhr.open():
void open(
DOMString method,
DOMString url,
optional boolean async, // <---
optional DOMString user,
optional DOMString password
);
The Web SQL Database spec does also define a Synchronous database API. However, it's only available to implementations of the WorkerUtils interface, defined primarily for Web Workers:
window.dbo = openDatabaseSync('POC','1.0','Proof-Of-Concept', 1024*1024);
var results;
window.dbo.transaction(function (trans) {
results = trans.executeSql('...');
});
If the environment running the script hasn't implemented this interface, then you're stuck with the asynchronous API and returning the result will not be feasible. You can't force blocking/waiting of asynchronous tasks for the reason you suspected:
Maybe my spinning isn't giving the callback chance to come back (See code below).