ccgus / CocoaScript

JavaScript + the Cocoa frameworks, and then ObjC brackets show up to party as well.
Other
618 stars 58 forks source link

Is there a way to use Objective C Block #32

Closed MForever78 closed 8 years ago

MForever78 commented 9 years ago

I want to use NSURLConnection sendAsynchronousRequest method, which requires a block as a callback handler. Is there a way CS can do this? Or what is the recommended way to handle async request?

Thanks.

ccgus commented 9 years ago

There's no good way to do async stuff in coscript right now.

abhibeckert commented 9 years ago

You should be able to create a modal window (perhaps with a cancel button to abort the network request, and maybe even a progress bar if you're really fancy) and then tell NSApp to "runModalForWindow" on it.

You need to put the NSURLConnection in NSModalPanelRunLoopMode mode or it won't work.

You can create a window with:

  var window = [[NSWindow alloc] init]
  [window setTitle:"Loading..."]
  [window setFrame:NSMakeRect(0, 0, 290, 86) display:false]

And add some text to it:

  var promptField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)]
  [promptField setEditable:false]
  [promptField setBordered:false]
  [promptField setDrawsBackground:false]
  [promptField setStringValue:"Please Wait"]
  [promptField sizeToFit]
  [promptField setFrame:NSMakeRect(20, 23, [promptField frame].size.width, [promptField frame].size.height)]
  [[window contentView] addSubview:promptField]

Then fire off your NSURLConnection using something like:

var conn = [[NSURLConnection alloc] initWithRequest:requst delegate:self startImmediately:false]
[conn scheduleRunLoop:[NSRunLoop currentLoop] forMode:"NSModalPanelRunLoopMode"]
[conn start]

And finally go modal:

[NSApp runModalForWindow:window]

When the URL connection receives a response, you need to do:

   [window orderOut:nil]
   [NSApp stopModal]
abhibeckert commented 9 years ago

(note: according to a stack overflow post from 4 years ago it's necessary to use abortModal instead of stopModal when you're working with NSURLConnection. That sounds like a bug to me, maybe it's fixed now)

MForever78 commented 9 years ago

@abhibeckert Thanks for the reply.

The problem is, although I can send a request and delegate it, I can't handle it afterward. That's because I didn't find a way to do something like:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // Append the new data to receivedData.
    // receivedData is an instance variable declared elsewhere.
    [receivedData appendData:data];
}

I have spending the whole day to do this. I'm developing a sketch plugin that require a network request, and sync request is unaffordable. I'm even considering writing an Objective-C framework and let my plugin load it in the runtime. That's really a pain.

MForever78 commented 9 years ago

I also tried this:

var handler = function(res, data, err) {};
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:handler]

I'm seeing that my server did receive an request, but after this Sketch just broke. I can't figure out whether it's Sketch's fault or cocoascript's or maybe Mocha's...

Disappointed...

abhibeckert commented 9 years ago

@MForever78 I suspect sendAsynchronousRequest:queue:completionHandler: will not set the mode to "NSModalPanelRunLoopMode" which would explain the issue you're having.

You're going to have to get connection:didRecieveData: to work.

I had a similar problem where I needed a target/action object for an NSButton (which is inside a modal window). For that I created a "COTarget" class in Objective-C which executes an inline javascript function:

https://gist.github.com/abhibeckert/dbeae1ea7716e2f49506 https://gist.github.com/abhibeckert/66f95f885f11ace75a01

You could create a similar class with delegate methods for NSURLConnectionDelegate. Put that in a mini framework/bundle and load it up, then at least most of your code can be in CocoaScript.

Alternatively, make it a synchronous request.

MForever78 commented 9 years ago

@abhibeckert Thanks a lot! I will definitely give it a try.

dongxinb commented 9 years ago

@abhibeckert Hello, I have tried the method COTarget you provided but there may be some problems. Here is my step:

  1. I found that the CocoaScript has implemented a similar class of COTarget called COSTarget (https://github.com/ccgus/CocoaScript/blob/master/src/framework/COSTarget.h), so I used the class.
  2. I build a simple framework TestFramework and put a class MyObject in it. Here is my class structure:

    @interface MyObject : NSObject
    @property (weak, nonatomic) id target;
    @property SEL action;
    - (void)test;
    @end
    
    @implementation MyObject
    - (void)test {
       [_target performSelector:_action withObject:nil];
    }
    @end
  3. Here is my code in CocoaScript

    framework('TestFramework');
    var ob = [MyObject new];
    [ob setCOSJSTargetFunction:function(res) {
       log('Selector Valid');
    }];
    [ob test];
    

And in result, I didn't get the log information 'Selector Valid' in the Concole.

Can you help to find the error of my code? Thanks.

tarngerine commented 8 years ago

you can do an async connection by hacking https://github.com/matt-curtis/MochaJSDelegate to implement a connection's delegates fwiw (altho currently broken in 3.4 bc it requires COScript.currentCOScript().setShouldKeepAround_(true);

MForever78 commented 8 years ago

Thanks @tarngerine .

Because the method I have found generally doesn't work for me, I choose to create a webview to do the async work on my project.

What I did is write a framework that create the webview and do the async work, and load the framework in the CocoaScript application. This works fine for me. Just comment in case anybody else needs the feature.

I'm closing this issue because the author doesn't seem to add the feature, and I finally find a (hard) way to achieve this.

diorahman commented 8 years ago

Hi @MForever78 could you show me your approach? Is is true, that by loading the webview you can stream the value from and to the evaluated javascript (and control the execution) inside the webview from cocoascript?

MForever78 commented 8 years ago

@diorahman There's a better workaround now. But the idea is the same.

Check out matt-curtis/MochaJSDelegate which saves you from writing Objective-C to create a webview. And matt-curtis/MochaJSWebScriptingObject to expose function to webview. The demo in their README works fine for me.

yiye commented 7 years ago

@dongxinb you can subclass NSButton , then trigger itself。