I am Trying to run html page with electron + eel.
I have successfully loaded the webpage + eel.js.
My Problem is whenever I try to use require module in the javascript of the HTML page. it gives me the below:
Uncaught ReferenceError: require is not defined
The sequence is, launching the webapp using runner.py ( eel.start ), then electron start
Also, if I set mainWindow.loadFile('templates/index.html') in main.js file, instead of mainWindow.loadURL('http://localhost:8000/index.html'). the render just work fine and can handle electron window from main html javascript file. but eel is not loaded in this case.
If I tried the steps which in the guide. the electron html page opens normally with eel.js loaded up. but can not handle the electron window from html javascript file
runner.py
import eel
import eel.browsers
eel.init('templates')
eel.browsers.set_path('electron', 'node_modules/electron/dist/electron')
eel.start('index.html', mode='electron' , port=8000 ,host='localhost',disable_cache=True)
main.js:
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
enableRemoteModule: true
}
})
// and load the index.html of the app.
// mainWindow.loadFile('templates/index.html')
mainWindow.loadURL('http://localhost:8000/index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
html javascript:
function login_check(){
var serverIP = document.getElementById('serverIP').value
var Username = document.getElementById('usernameEntry').value
var Password = document.getElementById('passwordEntry').value
var RememberMe = document.getElementById('remember-me-checkbox').checked
if(serverIP != "" && Username != "" && Password != ""){
document.querySelector('.progress-bar-container-div').classList.add('active')
}
else{
const window = require("electron").getCurrentWindow()
alert("Required Fields are empty.")
document.querySelector('.progress-bar-container-div').classList.remove('active')
window.minimize();
}
}
package.json:
{
"name": "electron-quick-start",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"repository": "https://github.com/electron/electron-quick-start",
"keywords": [
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "GitHub",
"license": "CC0-1.0",
"devDependencies": {
"electron": "^5.0.0"
}
}
error:
Related
Hi I am trying to run multiple python functions using the same linker JS file. I am using electron, python, node.JS, and HTML.
function execute_python() {
var path = require("path")
const city = 'XYZ';
var options = {
scriptPath : path.join(__dirname, '../python/'),
args : [city]
}
const {PythonShell} = require("python-shell");
//will run python script
var shell = new PythonShell('main.py', options);
//output of script
shell.on('message', function(message) {
swal(message);
})
}
/*
shell.end(function(err,code,signal))
if(err) throw err;
console.log('The exit code was:'+ code)
console.log('The exit signal was:'+ signal)
console.log('finished')
})
}
*/
function execute_python2() {
var path = require("path")
const city2 = 'XYZ';
var options2 = {
scriptPath : path.join(__dirname, '../python/'),
args : [city2]
}
const {PythonShell} = require("python-shell");
//will run python script
var please = new PythonShell('main.py', options2);
//output of script
please.on('message', function(message) {
swal(message);
})
}
For some reason, both buttons I press will give the same output.
Here is my python file.
import sys
city = sys.argv[1]
print("Hi there")
city2= sys.argv[0]
sys.stdout.flush()
Here is my html file.
<button onclick="execute_python()">Execute Python file</button>
<button onclick="execute_python2()">Execute Python file</button>
</body>
<script>
require('./index.js')
</script>
<script src="./js/linker.js"></script>
Let me know what I am missing here. I tried making two different arguments on the javascript side of things with having different arguments names in the options.
I'm working on creating a JupyterLab server-side extension from this template and am having trouble calling an imported python function in the post method of handlers.py. The post request is sent from my index.ts file with a body containing the relative path of the active notebook when the user presses the "Measure Energy Usage" button. Sending it works fine if I leave out the call to measure_cell2.measure() in handlers.py. However, when I try to call that function, I get these errors on the right from the post request:
In the terminal running jupyter lab, I get this error:
[W 2021-07-13 12:42:47.633 ServerApp] 404 POST /jlab-ext-example/hello?1626198167480 (127.0.0.1) 7.70ms referer=http://localhost:8888/lab/tree/jupyter_lab_ext/jlab_ext_example/nbtest2.ipynb
I know that abs_path is the correct path, because when I leave out the measure function call in handlers.py, simply print the path returned by os.path.abspath, and then run the measure function directly from the python console or the terminal using that path, it produces the desired output. The post method in handlers.py doesn't seem to be executing at all since print(abs_path) is never executed and it comes before the call to measure. What could be causing this error?
JupyterLab 3.0.16,
Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-143-generic x86_64),
Python 3.9.5,
Node v14.8.0,
tsc 2.7.2
index.ts:
import {
ILabShell,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '#jupyterlab/application';
import { IRetroShell } from '#retrolab/application';
import { ICommandPalette } from '#jupyterlab/apputils';
import { ILauncher } from '#jupyterlab/launcher';
import { requestAPI } from './handler';
import { ToolbarButton } from '#jupyterlab/apputils';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable } from '#lumino/disposable';
import { Title, Widget } from '#lumino/widgets';
var nbPath: string; // Holds relative path of notebook to convert
// Create button on the toolbar
export class ButtonExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
constructor(app: JupyterFrontEnd) {
this.app = app;
}
readonly app: JupyterFrontEnd
// Create the toolbar button
createNew(panel: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
let mybutton = new ToolbarButton({
label: 'Measure Energy Usage',
onClick: async () => {
// Post request to Jupyter server containing relative
// path to .ipynb file
const dataToSend = { file: nbPath };
try {
const reply = await requestAPI<any>('hello', {
body: JSON.stringify(dataToSend),
method: 'POST'
});
console.log(reply);
} catch (reason) {
console.error(
`Error on POST /jlab-ext-example/hello ${dataToSend}.\n${reason}`
);
}
}
});
// Add the toolbar button to the notebook toolbar
panel.toolbar.insertItem(10, 'MeasureEnergyUsage', mybutton);
console.log("MeasEnerUsage activated");
// The ToolbarButton class implements `IDisposable`, so the
// button *is* the extension for the purposes of this method.
return mybutton;
}
}
/**
* Initialization data for the server-extension-example extension.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'server-extension-example',
autoStart: true,
optional: [ILauncher],
requires: [ICommandPalette],
activate: async (
app: JupyterFrontEnd
) => {
console.log('JupyterLab extension server-extension-example is activated!');
const your_button = new ButtonExtension(app);
app.docRegistry.addWidgetExtension('Notebook', your_button);
// Get name and relative path of active notebook tab
var shell = app.shell as ILabShell | IRetroShell;
// When user changes tabs, update nbPath to be the relative path for current notebook
const onTitleChanged = (title: Title<Widget>) => {
nbPath = title.caption;
nbPath = nbPath.slice(nbPath.indexOf("Path: ") + 6, nbPath.indexOf("Last Saved") - 1);
console.log(nbPath);
};
// Watch for user changing tabs in JupyterLab
shell.currentChanged.connect((_: any, change: any) => {
const { oldValue, newValue } = change;
if (oldValue) {
oldValue.title.changed.disconnect(onTitleChanged);
}
if (newValue) {
newValue.title.changed.connect(onTitleChanged);
}
});
}
};
export default extension;
handlers.py:
import os
import json
import subprocess
from notebook.base.handlers import APIHandler
from notebook.utils import url_path_join
import tornado
from tornado.web import StaticFileHandler
import measure_cell2
class RouteHandler(APIHandler):
# The following decorator should be present on all verb methods (head, get, post,
# patch, put, delete, options) to ensure only authorized user can request the
# Jupyter server
#tornado.web.authenticated
def get(self):
self.finish(json.dumps({"data": "At the /jlab-ext-example/hello endpoint; finding and converting notebook"}))
#tornado.web.authenticated
def post(self):
# receive .ipynb file name and relative path from index.ts on client-side
# input_data is a dictionary with a key "file"
input_data = self.get_json_body()
# convert notebook to .py file with nbconvert
subprocess.run(["jupyter", "nbconvert", input_data["file"], "--to", "script"])
# get name and absolute path of converted .py file
python_file_name = input_data["file"]
extension_index = python_file_name.find(".")
python_file_name = python_file_name[0:extension_index] + ".py"
abs_path = os.path.abspath(python_file_name)
print(abs_path)
# measure code energy usage
measure_cell2.measure(abs_path)
# confirm conversion and measurement occurred
data = {"greetings": "Converted notebook to the python file {}; now measuring energy usage".format(python_file_name)}
self.finish(json.dumps(data))
def setup_handlers(web_app, url_path):
host_pattern = ".*$"
base_url = web_app.settings["base_url"]
# Prepend the base_url so that it works in a JupyterHub setting
route_pattern = url_path_join(base_url, url_path, "hello")
handlers = [(route_pattern, RouteHandler)]
web_app.add_handlers(host_pattern, handlers)
# Prepend the base_url so that it works in a JupyterHub setting
doc_url = url_path_join(base_url, url_path, "public")
doc_dir = os.getenv(
"JLAB_SERVER_EXAMPLE_STATIC_DIR",
os.path.join(os.path.dirname(__file__), "public"),
)
handlers = [("{}/(.*)".format(doc_url), StaticFileHandler, {"path": doc_dir})]
web_app.add_handlers(".*$", handlers)
measure_cell2.py:
import requests
def measure(file_path):
base_url = 'http://localhost:9898'
# send file and get ID
url_id = base_url + '/id'
print(file_path)
files = {'file': open(file_path, 'rb')} # opens file into buffer; rb = read buffer
getID = requests.post(url_id, files=files)
ID = getID.json().get('id')
print("Running Code at ", file_path)
print(ID)
# send ID and get gpu results
url_results_gpu = base_url + '/results/gpu?id={}'.format(ID)
gpu_results = requests.get(url_results_gpu)
print(gpu_results.text)
# send ID and get cpu results
url_results_cpu = base_url + '/results/cpu?id={}'.format(ID)
cpu_results = requests.get(url_results_cpu)
print(cpu_results.text)
Need a Python Script that will restart the kernel and rerun all the cells automatically without any human intervention.
I tried the following code below but needs a human intervention since it uses a toggle button.
from IPython.display import HTML, Javascript, display
def initialize():
display(HTML(
'''
<script>
code_show = false;
function restart_run_all(){
IPython.notebook.kernel.restart();
setTimeout(function(){
IPython.notebook.execute_all_cells();
}, 10000)
}
function code_toggle() {
if (code_show) {
$('div.input').hide(200);
} else {
$('div.input').show(200);
}
code_show = !code_show
}
</script>
<button onclick="code_toggle()">Click to toggle</button>
<button onclick="restart_run_all()">Click to Restart and Run all Cells</button>
'''
))
initialize()
modifying the code a bit seems to be working for me:
from IPython.display import HTML, Javascript, display
def initialize():
display(HTML(
'''
<script>
code_show = false;
function restart_run_all(){
IPython.notebook.kernel.restart();
setTimeout(function(){
IPython.notebook.execute_all_cells();
}, 10000)
}
function code_toggle() {
if (code_show) {
$('div.input').hide(200);
} else {
$('div.input').show(200);
}
code_show = !code_show
}
restart_run_all();
</script>
'''
))
Note: I just removed the buttons and called the restart_run_all() function before </ script>
I want to build an automation testing, so I have to know the errors that appear in the console of chrome.
there is an option to get the error lines that appear in the console?
In order to see the console: right click somewhere in the page, click "inspect element" and then go to "console".
I don't know C# but here's Java code that does the job, I hope you can translate it to C#
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class ChromeConsoleLogging {
private WebDriver driver;
#BeforeMethod
public void setUp() {
System.setProperty("webdriver.chrome.driver", "c:\\path\\to\\chromedriver.exe");
DesiredCapabilities caps = DesiredCapabilities.chrome();
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
caps.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
driver = new ChromeDriver(caps);
}
#AfterMethod
public void tearDown() {
driver.quit();
}
public void analyzeLog() {
LogEntries logEntries = driver.manage().logs().get(LogType.BROWSER);
for (LogEntry entry : logEntries) {
System.out.println(new Date(entry.getTimestamp()) + " " + entry.getLevel() + " " + entry.getMessage());
//do something useful with the data
}
}
#Test
public void testMethod() {
driver.get("http://mypage.com");
//do something on page
analyzeLog();
}
}
Pay attention to setUp method in above code. We use LoggingPreferences object to enable logging. There are a few types of logs, but if you want to track console errors then LogType.BROWSER is the one that you should use. Then we pass that object to DesiredCapabilities and further to ChromeDriver constructor and voila - we have an instance of ChromeDriver with logging enabled.
After performing some actions on page we call analyzeLog() method. Here we simply extract the log and iterate through its entries. Here you can put assertions or do any other reporting you want.
My inspiration was this code by Michael Klepikov that explains how to extract performance logs from ChromeDriver.
You can get logs this way:
Driver().Manage().Logs.GetLog();
By specifying what log you are interested in you can get the browser log, that is:
Driver().Manage().Logs.GetLog(LogType.Browser);
Also remember to setup your driver accordingly:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
driver = new ChromeDriver("path to driver", options);
This is the c# code for logging the brower log from chrome.
private void CheckLogs()
{
List<LogEntry> logs = Driver.Manage().Logs.GetLog(LogType.Browser).ToList();
foreach (LogEntry log in logs)
{
Log(log.Message);
}
}
here is my code for the actual log:
public void Log(string value, params object[] values)
{
// allow indenting
if (!String.IsNullOrEmpty(value) && value.Length > 0 && value.Substring(0, 1) != "*")
{
value = " " + value;
}
// write the log
Console.WriteLine(String.Format(value, values));
}
As per issue 6832 logging is not implemented yet for C# bindings. So there might not be an easy way to get this working as of now.
Here is a solution to get Chrome logs using the C#, Specflow and Selenium 4.0.0-alpha05.
Pay attention that the same code doesn't work with Selenium 3.141.0.
[AfterScenario]
public void AfterScenario(ScenarioContext context)
{
if (context.TestError != null)
{
GetChromeLogs(context); //Chrome logs are taken only if test fails
}
Driver.Quit();
}
private void GetChromeLogs()
{
var chromeLogs = Driver.Manage().Logs.GetLog(LogType.Browser).ToList();
}
public void Test_DetectMissingFilesToLoadWebpage()
{
try
{
List<LogEntry> logs = driver.Manage().Logs.GetLog(LogType.Browser).ToList();
foreach (LogEntry log in logs)
{
while(logs.Count > 0)
{
String logInfo = log.ToString();
if (log.Message.Contains("Failed to load resource: the server responded with a status of 404 (Not Found)"))
{
Assert.Fail();
}
else
{
Assert.Pass();
}
}
}
}
catch (NoSuchElementException e)
{
test.Fail(e.StackTrace);
}
}
You could do something like this in C#. It is a complete test case. Then print the console output as String i.e logInfo in your report. For some reason, Log(log.Message) from the solution above this one gave me build errors.So, I replaced it.
C# bindings to the Chrome console logs are finally available in Selenium 4.0.0-alpha05. Selenium 3.141.0 and prior do not have support.
Before instantiating a new ChromeDriver object, set the logging preference in a ChromeOptions object and pass that into ChromeDriver:
ChromeOptions options = new ChromeOptions();
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
ChromeDriver driver = new ChromeDriver(options);
Then, to write the Chrome console logs to a flat file:
public void WriteConsoleErrors()
{
string strPath = "C:\\ConsoleErrors.txt";
if (!File.Exists(strPath))
{
File.Create(strPath).Dispose();
}
using (StreamWriter sw = File.AppendText(strPath))
{
var entries = driver.Manage().Logs.GetLog(LogType.Browser);
foreach (var entry in entries)
{
sw.WriteLine(entry.ToString());
}
}
}
driver.manage().logs().get("browser")
Gets all logs printed on the console. I was able to get all logs except Violations. Please have a look here Chrome Console logs not printing Violations
Refering to a previously asked question, I would like to know how to get the title of the current active document.
I tried the script mention in the answers to the question above. This works, but only gives me the name of the application. For example, I am writing this question: When I fire up the script it gives me the name of the application, i.e. "Firefox". This is pretty neat, but does not really help. I would rather like to capture the title of my current active document. See the image.
Firefox title http://img.skitch.com/20090126-nq2egknhjr928d1s74i9xixckf.jpg
I am using Leopard, so no backward compatibility needed. Also I am using Python's Appkit to gain access to the NSWorkspace class, but if you tell me the Objective-C code, I could figure out the translation to Python.
Ok, I've got a solution which is not very satisfing, thats why I don't mark Koen Bok's answer. At least not yet.
tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell
tell application frontApp
if the (count of windows) is not 0 then
set window_name to name of front window
end if
end tell
Save as script and invoke it with osascript from the shell.
As far as I know your best bet is wrapping an AppleScript. But AppleScript is magic to me so I leave it as an exercise for the questioner :-)
This might help a little: A script to resize frontmost two windows to fill screen - Mac OS X Hints
In Objective-C, the short answer, using a little Cocoa and mostly the Carbon Accessibility API is:
// Get the process ID of the frontmost application.
NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
frontmostApplication];
pid_t pid = [app processIdentifier];
// See if we have accessibility permissions, and if not, prompt the user to
// visit System Preferences.
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
Boolean appHasPermission = AXIsProcessTrustedWithOptions(
(__bridge CFDictionaryRef)options);
if (!appHasPermission) {
return; // we don't have accessibility permissions
// Get the accessibility element corresponding to the frontmost application.
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
if (!appElem) {
return;
}
// Get the accessibility element corresponding to the frontmost window
// of the frontmost application.
AXUIElementRef window = NULL;
if (AXUIElementCopyAttributeValue(appElem,
kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
CFRelease(appElem);
return;
}
// Finally, get the title of the frontmost window.
CFStringRef title = NULL;
AXError result = AXUIElementCopyAttributeValue(window, kAXTitleAttribute,
(CFTypeRef*)&title);
// At this point, we don't need window and appElem anymore.
CFRelease(window);
CFRelease(appElem);
if (result != kAXErrorSuccess) {
// Failed to get the window title.
return;
}
// Success! Now, do something with the title, e.g. copy it somewhere.
// Once we're done with the title, release it.
CFRelease(title);
Alternatively, it may be simpler to use the CGWindow API, as alluded to in this StackOverflow answer.
refered to https://stackoverflow.com/a/23451568/11185460
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa
#import <Cocoa/Cocoa.h>
int
GetFrontMostAppPid(void){
NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
frontmostApplication];
pid_t pid = [app processIdentifier];
return pid;
}
CFStringRef
GetAppTitle(pid_t pid) {
CFStringRef title = NULL;
// Get the process ID of the frontmost application.
// NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
// frontmostApplication];
// pid_t pid = [app processIdentifier];
// See if we have accessibility permissions, and if not, prompt the user to
// visit System Preferences.
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
Boolean appHasPermission = AXIsProcessTrustedWithOptions(
(__bridge CFDictionaryRef)options);
if (!appHasPermission) {
return title; // we don't have accessibility permissions
}
// Get the accessibility element corresponding to the frontmost application.
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
if (!appElem) {
return title;
}
// Get the accessibility element corresponding to the frontmost window
// of the frontmost application.
AXUIElementRef window = NULL;
if (AXUIElementCopyAttributeValue(appElem,
kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
CFRelease(appElem);
return title;
}
// Finally, get the title of the frontmost window.
AXError result = AXUIElementCopyAttributeValue(window, kAXTitleAttribute,
(CFTypeRef*)&title);
// At this point, we don't need window and appElem anymore.
CFRelease(window);
CFRelease(appElem);
if (result != kAXErrorSuccess) {
// Failed to get the window title.
return title;
}
// Success! Now, do something with the title, e.g. copy it somewhere.
// Once we're done with the title, release it.
CFRelease(title);
return title;
}
static inline CFIndex cfstring_utf8_length(CFStringRef str, CFIndex *need) {
CFIndex n, usedBufLen;
CFRange rng = CFRangeMake(0, CFStringGetLength(str));
return CFStringGetBytes(str, rng, kCFStringEncodingUTF8, 0, 0, NULL, 0, need);
}
*/
import "C"
import (
"github.com/shirou/gopsutil/v3/process"
"reflect"
"unsafe"
)
//import "github.com/shirou/gopsutil/v3/process"
func cfstringGo(cfs C.CFStringRef) string {
var usedBufLen C.CFIndex
n := C.cfstring_utf8_length(cfs, &usedBufLen)
if n <= 0 {
return ""
}
rng := C.CFRange{location: C.CFIndex(0), length: n}
buf := make([]byte, int(usedBufLen))
bufp := unsafe.Pointer(&buf[0])
C.CFStringGetBytes(cfs, rng, C.kCFStringEncodingUTF8, 0, 0, (*C.UInt8)(bufp), C.CFIndex(len(buf)), &usedBufLen)
sh := &reflect.StringHeader{
Data: uintptr(bufp),
Len: int(usedBufLen),
}
return *(*string)(unsafe.Pointer(sh))
}
func main() {
pid := C.GetFrontMostAppPid()
ps, _ := process.NewProcess(int32(pid))
title_ref := C.CFStringRef(C.GetAppTitle(pid))
println(pid) // pid
println(ps.Name()) // process name
println(cfstringGo(title_ref)) // active window title
}
I then found this property wont change after it is called.
By this, only after we implement NSWorkspaceDidActivateApplicationNotification, we can monitor the change of activity window. But I didn't find any solution which can implement NSWorkspaceDidActivateApplicationNotification in golang.
A workaround method is compile one go program and call it by another go program. I then try full Objective-C code in here