I'm trying to create a WKNavigationDelegate for a WKWebView but have been unable to call the decisionHandler. Does anyone have any idea how to do this?
ObjC.registerSubclass({
name: "test",
methods: {
"webView:decidePolicyForNavigationAction:decisionHandler:": {
types: ["void", ["id", "id", "id"]],
implementation: function(wv, navigationAction, decisionHandler) {
decisionHandler($.WKNavigationActionPolicyAllow);
return;
}
}
}
});
var delegate = $['test'].alloc.init;
webView.navigationDelegate = delegate;
The code works up to the point where it prevents the navigation action but the decisionHandler is not called!
In the console I get the log:
Completion handler passed to -[test webView:decidePolicyForNavigationAction:decisionHandler:] was not called
Changing the type from "id" to "block" or "function" doesn't work.
I'd add a comment but confusing SO rules prohibit newbs from not answering (?!). Does adding protocols: ['WKNavigationDelegate'] to the registration help?
Your method is being called, and $.WKNavigationActionPolicyAllow shows as being == 1, but maybe the protocol will assist type inference by JSC.
Related
obviously I am new to RxSwift and though I consumed a lot of documentations and speeches, I think I am missing some fundamental concepts.
In my app I have a RESTful web service to load various resources but the base url of the web service is unknown at build/start time. Instead I have a "URL resolver" web service which I can call with my apps bundle, version and possible environment ("production", "debug" or any custom string entered in the apps debug settings) to obtain the base url I then use for the actual service.
My thinking was that I would create 2 services, one for the URL resolver and one for the actual web service which gives me my resources. The URL resolver would have a Variable and a Observable. I use the variable to signal the need to refresh the base url via a web service call to the URL resolver. I do this by observing the variable and filter only for true values. A function in the service class set the variables value to true (initially it is false) and inside an observer of the filtered variable, I make the web service call in another Observable (this example uses a dummy JSON web service):
import Foundation
import RxSwift
import Alamofire
struct BaseURL: Codable {
let title: String
}
struct URLService {
private static var counter = 0
private static let urlVariable: Variable<Bool> = Variable(false)
static let urlObservable: Observable<BaseURL> = urlVariable.asObservable()
.filter { counter += 1; return $0 }
.flatMap { _ in
return Observable.create { observer in
let url = counter < 5 ? "https://jsonplaceholder.typicode.com/posts" : ""
let requestReference = Alamofire.request(url).responseJSON { response in
do {
let items = try JSONDecoder().decode([BaseURL].self, from: response.data!)
observer.onNext(items[0])
} catch {
observer.onError(error)
}
}
return Disposables.create() {
requestReference.cancel()
}
}
}
static func getBaseUrl() {
urlVariable.value = true;
}
static func reset() {
counter = 0;
}
}
Now the problem is that sometimes it can happen that a web service call fails and I would need to show the error to the user so a retry can be made. I thought that the onError was useful for this but it seems to kills all the subscribers forever.
I could put the subscribing in its own function and inside the error handler of the Observer, I could show a alert and then call the subscribe function again like so:
func subscribe() {
URLService.urlObservable.subscribe(onNext: { (baseURL) in
let alert = UIAlertController(title: "Success in Web Service", message: "Base URL is \(baseURL.title)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Try again!", style: .default, handler: { action in
URLService.getBaseUrl()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}, onError: { error in
let alert = UIAlertController(title: "Error in Web Service", message: "Something went wrong: \(error.localizedDescription)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Yes", style: .default, handler: { action in
URLService.reset()
self.subscribe()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
VesselService.reset()
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}).disposed(by: disposeBag)
}
Then in my AppDelegate I would call
subscribe()
URLService.getBaseUrl()
The problem is that all other observers get killed on an error as well but since the the only other observer on the URLService.urlObservable is my other web service class, I guess I could implement the same style subscribe function in there as well.
I read that some people suggest to return a Result enum which has 2 cases: the actual result (.success(result: T)) or an error (.error(error: Error)).
So what is the better way of handling errors web service errors in Rx? I cant wrap my head around this problem and I'm trying for 2 days to understand it. Any ideas or suggestions?
Update
It just came to my mind that I could ignore errors from the web service calls completely and instead post any error to a global "error" variable which my app delegate could observe to show alerts. The "error" could reference the function which initially caused it so a retry could be made. I'm still confused and not sure what I should do. :/
Update 2
I think I might found a working solution. As I am still a beginner to Rx and RxSwift, I'm happy to take improvement suggestions. As I was writing the actual code, I splitted my call chain in two parts:
The part where I make the web service calls
The part where I click a button and process the result of the web service, whether it is an error or a success
In the part where I click the button and process the result, I use catchError and retry as suggested in the comments. The code looks like this:
let userObservable = URLService
.getBaseUrl(environment: UserDefaults.standard.environment) //Get base url from web service 1
.flatMap({ [unowned self] baseURL -> Observable<User> in
UserService.getUser(baseURL: baseURL,
email: self.usernameTextField.text!,
password: self.passwordTextField.text!) //Get user from web service 2 using the base url from webservice 1
})
signInButton
.rx
.tap
.throttle(0.5, scheduler: MainScheduler.instance)
.flatMap({ [unowned self] () -> Observable<()> in
Observable.create { observable in
let hud = MBProgressHUD.present(withTitle: "Signing in...");
self.hud = hud
observable.onNext(())
return Disposables.create {
hud?.dismiss()
}
}
})
.flatMap({ () -> Observable<User> in
return userObservable
})
.catchError({ [unowned self] error -> Observable<User> in
self.hud?.dismiss()
self.handleError(error)
return userObservable
})
.retry()
.subscribe(onNext: { [unowned self] (user) in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
self.hud?.dismiss()
})
.disposed(by: disposeBag)
The trick was to move the call to the two web services out of the cain into their own variable so I can re-call it at any time. When I now return the "userObservable" and an error happens during the web service call, I can show the error in the catchError and return the same "userObservable" for the next retry.
At the moment this only properly handles errors when they occur in the web service call chain so I think I should make the button tap a driver.
Okay so for everyone who comes here, you probably have a lack of understanding or a misconception of how the Rx world is supposed to work. I still find it sometimes confusing but I found a way better solution than what I posted in my original question.
In Rx, a error "kills" or rather completes all observers in the chain and that is actually a good thing. If there are expected errors like API error in web service calls, you should either try to handle them where they occur or treat them like expected values.
For example, your observer could return a optional type and subscribers could filter for the existence of values. If an error in the API call occurs, return nil. Other "error handlers" could filter for nil values to display error messages to the user.
Also viable is to return a Result enum with two cases: .success(value: T) and .error(error: Error). You treat the error as a acceptable result and the observer is responsible for checking if it should display a error message or the success result value.
Yet another option, which surely is not the best as well but works it to simply nest the call which you expect to fail inside the subscriber of the call which must not be affected. In my case that is a button tap which causes a call to a web service.
The "Update 2" of my original post would become:
signInButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] () in
log.debug("Trying to sign user in. Presenting HUD")
self.hud = MBProgressHUD.present(withTitle: "Signing in...");
self.viewModel.signIn()
.subscribe(onNext: { [unowned self] user in
log.debug("User signed in successfully. Dismissing HUD")
self.hud?.dismiss()
}, onError: { [unowned self] error in
log.error("Failed to sign user in. Dismissing HUD and presenting error: \(error)")
self.hud?.dismiss()
self.handleError(error)
}).disposed(by: self.disposeBag)
}).disposed(by: self.disposeBag)
The MVVM view model makes the calls to the web serivces like so:
func signIn() -> Observable<User> {
log.debug("HUD presented. Loading BaseURL to sign in User")
return URLService.getBaseUrl(environment: UserDefaults.standard.environment)
.flatMap { [unowned self] baseURL -> Observable<BaseURL> in
log.debug("BaseURL loaded. Checking if special env is used.")
if let specialEnv = baseURL.users[self.username.value] {
log.debug("Special env is used. Reloading BaseURL")
UserDefaults.standard.environment = specialEnv
return URLService.getBaseUrl(environment: specialEnv)
} else {
log.debug("Current env is used. Returning BaseURL")
return Observable.just(baseURL)
}
}
.flatMap { [unowned self] baseURL -> Observable<User> in
log.debug("BaseURL to use is: \(baseURL.url). Now signing in User.")
let getUser = UserService.getUser(baseURL: baseURL.url, email: self.username.value, password: self.password.value).share()
getUser.subscribe(onError: { error in
UserDefaults.standard.environment = nil
}).disposed(by: self.disposeBag)
return getUser
}
.map{ user in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
return user
}
}
First I was thinking to only call the view models signIn() function when pressing the button but since there should be no UI code in the view model, I figured that presenting and dismissing the HUD is the responsibility of the ViewController.
I think this design is now pretty solid. The button observer never completes and can continue to send events forever. Earlier, if there was a second error, it might happen that the button observer died and my logs showed that the userObservable was executed twice, which must also not be happen.
I just wonder if there is a better way then nesting the subscribers.
I've been banging my head against deserializing data with Ember. I feel like I've set it up right but I keep getting the same error. I'm trying to use the EmbeddedRecords Mixin, but it simply hasn't worked for me. Below is my debug data.
DEBUG: Ember : 1.6.1
DEBUG: Ember Data : 1.0.0-beta.7+canary.b45e23ba
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.10.2
DEBUG: Model Fragments : 0.2.2
Here is a simple setup of what I've been doing. I have my model defined like this -
App.Subject = DS.Model.extend({
title: DS.attr('string'),
sections: DS.hasMany('section')
});
App.Section = DS.Model.extend({
title: DS.attr('string'),
subject: DS.belongsTo('subject')
});
App.SubjectSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
sections: { embedded: 'always' }
}
});
and here is the format of the JSON payload I'm sending for a 'show'
{
"subject": {
"_id":"549987b098909eef0ac2d691",
"title":"Maths",
"sections":[{
"title":"Precalc",
"_id":"549987b098909eef0ac2d693"
}, {
"title":"Calc",
"_id":"549987b098909eef0ac2d692"
}],"__v":0
}
}
I get the errors in the console
Error while processing route: subjects.show undefined is not a function TypeError: undefined is not a function
at Ember.Mixin.create.extractSingle (http://localhost:3300/js/highered.js:2043:25)
at apply (http://localhost:3300/js/highered.js:20664:27)
at superWrapper [as extractSingle] (http://localhost:3300/js/highered.js:20240:15)
at Ember.Object.extend.extractFind (http://localhost:3300/js/highered.js:4007:21)
at Ember.Object.extend.extract (http://localhost:3300/js/highered.js:3892:37)
at http://localhost:3300/js/highered.js:11864:34
at invokeCallback (http://localhost:3300/js/highered.js:23228:19)
at publish (http://localhost:3300/js/highered.js:22898:9)
at publishFulfillment (http://localhost:3300/js/highered.js:23318:7)
at http://localhost:3300/js/highered.js:28736:9
highered.js:16581 undefined is not a function TypeError: undefined is not a function
at Ember.Mixin.create.extractSingle (http://localhost:3300/js/highered.js:2043:25)
at apply (http://localhost:3300/js/highered.js:20664:27)
at superWrapper [as extractSingle] (http://localhost:3300/js/highered.js:20240:15)
at Ember.Object.extend.extractFind (http://localhost:3300/js/highered.js:4007:21)
at Ember.Object.extend.extract (http://localhost:3300/js/highered.js:3892:37)
at http://localhost:3300/js/highered.js:11864:34
at invokeCallback (http://localhost:3300/js/highered.js:23228:19)
at publish (http://localhost:3300/js/highered.js:22898:9)
at publishFulfillment (http://localhost:3300/js/highered.js:23318:7)
at http://localhost:3300/js/highered.js:28736:9
Which as best I can tell is directly related to extractSingle at the this.keyForAttribute method
extractSingle: function(store, primaryType, payload, recordId, requestType) {
var root = this.keyForAttribute(primaryType.typeKey),
partial = payload[root];
updatePayloadWithEmbedded(store, this, primaryType, partial, payload);
return this._super(store, primaryType, payload, recordId, requestType);
},
although an interesting thing to note is that the error occurs at extractArray when I am using the subjects index route, which return the json above but with array brackets as well.
extractArray: function(store, type, payload) {
var root = this.keyForAttribute(type.typeKey),
partials = payload[pluralize(root)];
forEach(partials, function(partial) {
updatePayloadWithEmbedded(store, this, type, partial, payload);
}, this);
return this._super(store, type, payload);
}
Which makes me think that Ember Data is having trouble recognizing the format. This happens any time I define a serializer for a model, not just when I enable embedded records.
I'm hoping someone will be able to explain this. As a final note I've been using the Ember Data Model Fragments library as well, but I disabled that and still got this error so I don't think that is it.
The Embedded Records mixin doesn't work with the RESTSerializer before beta 9.
You can view the state of it here Ember-data embedded records current state?
You'll also want to be wary of updating ember or ember data without the other version in certain circumstances. Ember Data cannot read property 'async' of undefined
I have done a service that gets a json file from the server with the translated values of the labels of my webapp. Seems to work fine:
mobilityApp.service('serveiTraduccions', function($resource) {
this.getTranslation = function($scope) {
var languageFilePath = 'traduccions/traduccio_en.json';
$resource(languageFilePath).get(function (data) {
$scope.translation = data;
});
};
});
What I am trying to do is acces that "$scope.translation" from my controler, I tried all and nothing worked. The object is saved in my $scope as you can see:
how can I get the values of the "registroBtnRegistro", "registroErrorRegistro" etc ?
Thanks in advance !
I tried:
console.log($scope.translation); -> undefined
console.log($scope['translation']); -> undefined
console.log($scope.translation.registroBtnRegistro); -> TypeError:
Cannot read property 'registroBtnRegistro' of undefined
console.log($scope.translation['registroBtnRegistro']); -> TypeError:
Cannot read property 'registroBtnRegistro' of undefined
Maybe you're trying to access these values from another $scope that not inherits the scope where you've created your translation model.
Try to assign this model directly to $rootScope, so you can access it from every scope:
mobilityApp.service('serveiTraduccions', function($resource, $rootScope) {
this.getTranslation = function() {
var languageFilePath = 'traduccions/traduccio_en.json';
$resource(languageFilePath).get(function (data) {
$rootScope.translation = data;
});
};
});
this answer is a blind attempt because your original post lacks basic information like the call from the controller.
we can refine it until we make it work.
First, you should be returning something from your method:
mobilityApp.service('serveiTraduccions', function($resource) {
this.getTranslation = function() {
var languageFilePath = 'traduccions/traduccio_en.json';
return $resource(languageFilePath);
};
});
You are using $resource but you might as well use basic $http.get(). at least it doesn't look like a restful api to me.
In any case, because it's an asynchronous request, it will not return the list of translated strings, but a resource "class" that allows methods like get, delete or the more general query():
from the docs: default methods are
{ 'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'} };
sidenote: injecting $scope in a service doesn't make much sense to me: services are used to encapsulate common logic accross components. However, you can pass a scope instance as a parameter.
Then, the controller that uses this should have the service injected and use a callback to get the results when they have arrived (asynchronous operation!):
TraduccioCtrl ... {
$scope.translation = {}; // avoid undefined when the view just loads
ServeiTraduccions.getTranslation.query(function (response) {
$scope.translation = response; // and angular's two-way data binding will probably do the rest
});
}
The Angular docs about ng-resource have a working example. Other questions in SO have addressed this already too, like Using AngularJS $resource to get data
I have a JSONStore like :
OrdersStore = Ext.extend(Ext.data.JsonStore, {
constructor: function(cfg) {
cfg = cfg || {};
OrdersStore.superclass.constructor.call(this, Ext.apply({
storeId: 'ordersStore',
url: '/ajaxSupport.action',
root: 'rows',
baseParams: {
action: 'getorderlegsearchgrid'
},
fields: [
{
name: 'orderId'
}
]
},
cfg));
}
});
new OrdersStore();
This store is attached to a grid : 'pendingOrdersGrid'.
When I do:
alert(Ext.util.JSON.encode(this.pendingOrdersGrid.getStore().getAt(0)));
I hope to get the first record. But I get 'null'
I can't give you a complete answer from this information but some hints:
don't extend a store with a fixed storeId, url or fields! That's really bad design
if possible use browser that supports a console (Firefox with firebug or IE with developer toolbar [or FF4/IE9]) and debug the content of your store in the console.
to read the content of a record try something like this.pendingOrdersGrid.getStore().getAt(0).data.orderId
Whats wrong in below JSON Object definition
I am trying to create a new JSON Object like below.
So that my idea is to access COMPANYSETUP.HourSetup.initiate();
Not sure the error ?
COMPANYSETUP = {
initiated : false,
companyId : "",
initiate : function() {
if(!initiated){
// TO DO
initiated = true;
} },
HourSetup = {
initiated : false,
hourId : "",
initiate : function() {
if(!initiated){
// TO DO
initiated = true;
}
}
}
};
Assuming you want a javascript object and not JSON, which disallows functions,
HourSetup =
Should be changed to:
HourSetup :
Also, as JonoW points out, your single line comments are including some of your code as the code is formatted in the post.
There's a "=" that shouldn't be there. Change it to ":"
JSON is a form of JavaScript deliberately restricted for safety. It cannot include dangerous code elements like function expressions.
It should work OK in plain eval()ed JavaScript, but it's not JSON.