Recently I have been trying to bind my application to CEF, the process was slow but I made some progress over the last days. However there is one thing I just can't get to work.
What I want to achieve is:
1. Registering a callback function within Javascript (These functions will be called when an event happens in my application)
2. Save the callback.
3. When the event occurs, execute the registered callback.
Now I got step 1 and 2 to work, but step 3 is where the problem is.
I tested it first by registering a callback and then calling it with an executeCallback() function in Javascript and it worked. But whenever I try to execute the callback from within C++ it doesn't work.
I create the setEventCallback in the onContextCread()
CefRefPtr<CefV8Value> object = context->GetGlobal();
CefRefPtr<CefV8Value> simplixObj = CefV8Value::CreateObject(NULL);
object->SetValue("simplix", simplixObj, V8_PROPERTY_ATTRIBUTE_NONE);
func = CefV8Value::CreateFunction("setEventCallback", $simplixHandler);
simplixObj->SetValue("setEventCallback", func, V8_PROPERTY_ATTRIBUTE_NONE);
With $simplixHandler being an instance of my own handler.
Here is the setEventCallback in my handler:
bool SimplixHandler::setEventCallback(CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
if (arguments.size() == 2 && arguments[0]->IsString() && arguments[1]->IsFunction()) {
//Get name of callback
std::string callbackName = arguments[0]->GetStringValue();
//Get the current context
CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
//Insert into callbacks
$callbacks[callbackName] = std::make_pair(context, arguments[1]);
return true;
}
else if(arguments.size() != 2) {
exception = CefString("Invalid amount of parameters. Expected 2, received " + arguments.size());
return false;
}
else {
exception = CefString("Incompatible parameter types, expected (string, function).");
return false;
}
I then try to execute the callbacks by calling executeCallback:
bool SimplixHandler::executeCallback(std::string name, CefRefPtr<CefListValue> list) {
bool handled = false;
if (!$callbacks.empty() && $callbacks.count(name) != 0) {
std::pair<CefRefPtr<CefV8Context>, CefRefPtr<CefV8Value>> pair = $callbacks[name];
CefRefPtr<CefV8Context> context = pair.first;
CefRefPtr<CefV8Value> func = pair.second;
context->Enter();
CefV8ValueList callArgs;
//First argument is function name
CefRefPtr<CefV8Value> args = CefV8Value::CreateArray(list->GetSize());
setList(list, args);
callArgs.push_back(args);
CefRefPtr<CefV8Value> retval = func->ExecuteFunction(NULL, callArgs);
if (retval.get()) {
if (retval->IsBool())
handled = retval->GetBoolValue();
}
context->Exit();
}
return handled;
}
I call this function from within my SimplixHandler, however I call callbacks by calling the specific event function, example:
bool SimplixHandler::notifyDirectoryChanged(std::string directory) {
CefRefPtr<CefListValue> list = CefListValue::Create();
list->SetSize(1);
list->SetString(0, directory);
return executeCallback("onDirectoryChange", list);
}
And finally my Javascript:
function dirChange(dir) {
document.getElementById("filename").innerHTML = "It's something";
}
simplix.setEventCallback("onDirectoryChange", dirChange);
I know the event gets called (because I first had nullpointer exception due to wrong linking of classes, that's fixed now), but the Javascript Callback does not get called.
Does anyone know what I am doing wrong?
Thanks in advance.
you can use any function in html code for callback.
Renderer:
bool SimpleApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message)
{
std::string message_name = message->GetName();
if( message_name == "FUNC")
{
//AfxMessageBox("stop");
//browser->GetMainFrame()->ExecuteJavaScript("super_test('works!');", browser->GetMainFrame()->->GetURL(), 0);
CefRefPtr<CefV8Value> object = m_context->GetGlobal();
CefRefPtr<CefV8Context> v8context = browser->GetMainFrame()->GetV8Context();
v8context->Enter();
CefRefPtr<CefV8Value> func_test = object->GetValue("super_test");
if(func_test == NULL)
{
v8context->Exit();
return false;
}
CefV8ValueList arguments;
CefRefPtr<CefV8Value> test = CefV8Value::CreateString("hello from c++");
arguments.push_back(test);
CefRefPtr<CefV8Value> retval = func_test->ExecuteFunction(NULL, arguments);
v8context->Exit();
return true;
}
return false;
}
jScript:
<script>
function super_test(resp)
{
alert(resp);
return "7777";
}
browser:
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create("FUNC");
SimpleHandler::GetInstance()->GetBrowser()->SendProcessMessage(PID_RENDERER, message);
Related
JS Function
I write a function to check if user in sharepoint group in javascript
function IsCurrentUserMemberOfGroup(groupName, OnComplete) {
var currentContext = new SP.ClientContext.get_current();
var currentWeb = currentContext.get_web();
var currentUser = currentContext.get_web().get_currentUser();
currentContext.load(currentUser);
var allGroups = currentWeb.get_siteGroups();
currentContext.load(allGroups);
currentContext.load(allGroups, 'Include(Users)');
currentContext.executeQueryAsync(OnSuccess, OnFailure);
function OnSuccess(sender, args) {
var userInGroup = false;
var groupEnumerator = allGroups.getEnumerator();
while (groupEnumerator.moveNext()) {
var oGroup = groupEnumerator.get_current();
if (groupName == oGroup.get_title()) {
var allUsers = oGroup.get_users();
var userEnumerator = allUsers.getEnumerator();
while (userEnumerator.moveNext()) {
var oUser = userEnumerator.get_current();
if (oUser.get_id() == currentUser.get_id()) {
userInGroup = true;
break;
}
}
}
}
OnComplete(userInGroup);
}
function OnFailure(sender, args) {
OnComplete(false);
} }
Usage
I use it in another function, wish to get the bool value of OnComplete and return it.
function SetButtonPermission() {
var isInGroup;
IsCurrentUserMemberOfGroup("Global", function(isCurrentUserInGroup) {
isInGroup = isCurrentUserInGroup;
});
return isInGroup; }
Question
It seems like I cannot get the bool isCurrentUserInGroup because it alert "isInGroup is undetified".
So How Can I Get The bool value ?
Similar to the answer provided here, when you're dealing with asynchronous function calls and callbacks, you'll be better off injecting data/logic into your callback function instead of returning data out from it.
The alternative is to push the "return" data into a global variable, or at least to a variable accessible within the same scope as the callback's execution, and delay execution of dependent logic until after the callback has executed.
You might want to look into JavaScript promises to see how script authors typically handle asynchronous code.
Trying to do something that in pseudo code would look like this:
(function(scope) {
scope.doSomenthin = function() {
if (x === y && this.onfinish) {
// If exists, run onfinish, should return 'fin'
this.onfinish();
}
}
})(scope);
window.scope = window.scope || (window.scope = {});
scope.doSomenthin().onfinish = function(){return 'fin'}
At run time if onfinish exists, run that function. Tried using getters/setter but at that point it will return undefined. Setting a timeout works but its not something I wish to do.
Any other ideas? Thanks.
I'm not sure if I completely understand the question, but I think what you want comes down to setting the context for the functions you are calling. Is this what you are after?
//create a function that accesses an object's properties and methods with 'this'
var doSomethin = function() {
var result = "nonfinish";
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
result = this.onfinish();
}
return result;
}
//add an 'onfinish' method to the 'scope' object
scope = {
onfinish: function(){return 'fin'}
}
//run the accessor function in the window context
alert(doSomethin());
//run the accessor function in scope's context
alert(doSomethin.call(scope));
I see several mistakes with your code. This may be the results you are trying to achieve..
window.scope = window.scope || (window.scope = {});
scope.onfinish = function(){return 'fin'};
(function(scope) {
scope.doSomenthin = function() {
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
return this.onfinish();
}
}
})(scope);
alert(scope.doSomenthin());
When you create the temporary scope here you give scope as a
parameter. But scope is not defined yet.
(function(scope) {
scope.doSomenthin = function() {
if (x === y && this.onfinish) {
// If exists, run onfinish, should return 'fin'
this.onfinish();
}
}
})(scope);
Your scope.doSomenthin function doesn't return any value. Because
of that the value of scope.doSomenthin() is undifined. Therefore
with scope.doSomenthin().onfinish = function(){return 'fin'} you
are trying to set a property of undifined.
What you want to approach is similar to event-driven programming. Don't just call the function right away, register it as an event handler instead. The following pseudo-code only shows my idea. It's not complete
//register the function here, instead of calling it immediately
event = document.createEvent("HTMLEvents");
event.initEvent("myEvent", true, true);
document.addEventListener("myEvent", function(e) {
e.scope.doSomenthin = function() {
if (this.onfinish) {
// If exists, run onfinish, should return 'fin'
return this.onfinish();
}
}
});
......
//call the handler to handle the below event
window.scope = window.scope || (window.scope = {});
scope.doSomenthin().onfinish = function(){return 'fin'}
event.scope = scope;
document.body.dispatchEvent(event);
The above code is kind of silly. You have to design where to put and trigger the events.
I would like to measure the computing time of methods.
A nice way is (How do you performance test JavaScript code?) with console.time('Function #1'); and console.timeEnd('Function #1');
My idea is to add these console outputs on lifecycle-methods. In this case using SAPUI5 like createContent:funtion(){}; methods.
This should be possible with AOP using before() and after() to runt the time counting.
Which AOP framework would you suggest and how to implement it with the need of modifying the identification string "Function #1" automatically?
There actually is no need for aspects in Javascript since you can change any function of any object at any time. JavaScript prototypes allows you to manipulate method implementations of all instances of an object at runtime. Here are two approaches for what you plan.
You could use a generic wrapper function:
var measureId = 0;
var fnMeasureFunction = function(fnToMeasure) {
console.time('measure'+ measureId);
fnToMeasure();
console.timeEnd('measure'+ measureId);
measureId++;
}
Admittedly that requires you to change your actual code...
For static functions or functions that belong to a prototype you could also do sth. like this from the outside without the need of any change to your existing code:
// any static function
var measureId = 0;
var fnOriginalFunction = sap.ui.core.mvc.JSViewRenderer.render;
sap.ui.core.mvc.JSViewRenderer.render = function() {
console.time('measure'+ measureId);
fnOriginalFunction.apply(this, arguments);
console.timeEnd('measure'+ measureId);
measureId++;
}
// any prototype function
var fnOriginalFunction = sap.m.Button.prototype.ontouchstart;
sap.m.Button.prototype.ontouchstart= function() {
console.time('measure'+ measureId);
fnOriginalFunction.apply(this, arguments);
console.timeEnd('measure'+ measureId);
measureId++;
}
This should be possible with AOP using before() and after() to runt the time counting.
As it already got mentioned, one really is not in need of real Aspect-oriented Programming
in order to solve such tasks in JavaScript. But this language might deserve some more standardized
method-modifiers in addition to the already existing bind method.
Please check back with my 2 most recent posts on this matter:
sandwich pattern in javascript code
Can you alter a Javascript function after declaring it?
... and how to implement it with the need of modifying the identification string "Function #1" automatically?
One does not need to since the console's time / timeEnd functionality only has to have
identical entry and exit points for measuring time (like the start/stop trigger of a stopwatch).
So one gets along with exactly the reference of the function/method one is currently running/measuring.
In order to solve the given task I will suggest around only instead of both before and
after for the former generates less overhead. The next code block exemplarily shows a
possible prototypal implementation. It also is the base for the afterwards following example
that finally might solve the OP's task.
(function (Function) {
var
isFunction = function (type) {
return (
(typeof type == "function")
&& (typeof type.call == "function")
&& (typeof type.apply == "function")
);
},
getSanitizedTarget = function (target) {
return ((target != null) && target) || null;
}
;
Function.prototype.around = function (handler, target) { // [around]
target = getSanitizedTarget(target);
var proceed = this;
return (isFunction(handler) && isFunction(proceed) && function () {
return handler.call(target, proceed, handler, arguments);
}) || proceed;
};
}(Function));
The next example takes into account that method-modification essentially relies on
functionality that is bound to an object. It is not just function wrapping. In order
to not loose the context a method is operating on, context has to be delegated /
passed around as target throughout all operations.
For this the example does not modify calculate since it is not bound to an object
but it modifies trigger instead.
var testObject = {
calculate: function (hugeInteger) {
var
i = hugeInteger,
k = 0
;
while (i--) {
k++;
}
return k;
},
trigger: function (hugeInteger) {
this.result = this.calculate(hugeInteger);
},
result: -1
};
console.log("testObject.result : ", testObject.result);
console.log("testObject.trigger(Math.pow(2, 26)) : ", testObject.trigger(Math.pow(2, 26))); // takes some time.
console.log("testObject.result : ", testObject.result);
console.log("testObject.someTrigger(0) : ", testObject.trigger(0)); // logs immediately after.
console.log("testObject.result : ", testObject.result);
testObject.trigger = testObject.trigger.around(function (proceed, interceptor, args) {
// before:
console.time(proceed);
// proceed:
proceed.apply(this, args);
// after:
console.timeEnd(proceed);
}, testObject); // omitting the 2nd argument - the [target] object - might break code that did work before.
console.log("testObject.trigger(Math.pow(2, 26)) : ", testObject.trigger(Math.pow(2, 26)));
console.log("testObject.result : ", testObject.result);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
(function (Function) {
var
isFunction = function (type) {
return (
(typeof type == "function")
&& (typeof type.call == "function")
&& (typeof type.apply == "function")
);
},
getSanitizedTarget = function (target) {
return ((target != null) && target) || null;
}
;
Function.prototype.around = function (handler, target) { // [around]
target = getSanitizedTarget(target);
var proceed = this;
return (isFunction(handler) && isFunction(proceed) && function () {
return handler.call(target, proceed, handler, arguments);
}) || proceed;
};
}(Function));
</script>
It looks like "$smth is not a function" is a very common problem with JavaScript, yet after looking through quite a few threads I still cannot understand what is causing it in my case.
I have a custom object, defined as:
function Scorm_API_12() {
var Initialized = false;
function LMSInitialize(param) {
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
return "false";
}
// some more functions, omitted.
}
var API = new Scorm_API_12();
Then in a different script I am trying to use this API in the following way:
var API = null;
function ScormProcessInitialize(){
var result;
API = getAPI();
if (API == null){
alert("ERROR - Could not establish a connection with the API.");
return;
}
// and here the dreaded error pops up
result = API.LMSInitialize("");
// more code, omitted
initialized = true;
}
The getAPI() stuff, looks like this:
var findAPITries = 0;
function findAPI(win)
{
// Check to see if the window (win) contains the API
// if the window (win) does not contain the API and
// the window (win) has a parent window and the parent window
// is not the same as the window (win)
while ( (win.API == null) &&
(win.parent != null) &&
(win.parent != win) )
{
// increment the number of findAPITries
findAPITries++;
// Note: 7 is an arbitrary number, but should be more than sufficient
if (findAPITries > 7)
{
alert("Error finding API -- too deeply nested.");
return null;
}
// set the variable that represents the window being
// being searched to be the parent of the current window
// then search for the API again
win = win.parent;
}
return win.API;
}
function getAPI()
{
// start by looking for the API in the current window
var theAPI = findAPI(window);
// if the API is null (could not be found in the current window)
// and the current window has an opener window
if ( (theAPI == null) &&
(window.opener != null) &&
(typeof(window.opener) != "undefined") )
{
// try to find the API in the current window�s opener
theAPI = findAPI(window.opener);
}
// if the API has not been found
if (theAPI == null)
{
// Alert the user that the API Adapter could not be found
alert("Unable to find an API adapter");
}
return theAPI;
}
Now, the API is probably found, because I do not get the "Unable to find..." message, the code proceeds to try to initialize it. But firebug tells me API.LMSInitialize is not a function, and if I try to debug it with alert(Object.getOwnPropertyNames(API));, it gives me a blank alert.
What am I missing?
For more generic advice on debugging this kind of problem MDN have a good article TypeError: "x" is not a function:
It was attempted to call a value like a function, but the value is not
actually a function. Some code expects you to provide a function, but
that didn't happen.
Maybe there is a typo in the function name? Maybe the object you are
calling the method on does not have this function? For example,
JavaScript objects have no map function, but JavaScript Array object
do.
Basically the object (all functions in js are also objects) does not exist where you think it does. This could be for numerous reasons including(not an extensive list):
Missing script library
Typo
The function is within a scope that you currently do not have access to, e.g.:
var x = function(){
var y = function() {
alert('fired y');
}
};
//the global scope can't access y because it is closed over in x and not exposed
//y is not a function err triggered
x.y();
Your object/function does not have the function your calling:
var x = function(){
var y = function() {
alert('fired y');
}
};
//z is not a function error (as above) triggered
x.z();
Your LMSInitialize function is declared inside Scorm_API_12 function. So it can be seen only in Scorm_API_12 function's scope.
If you want to use this function like API.LMSInitialize(""), declare Scorm_API_12 function like this:
function Scorm_API_12() {
var Initialized = false;
this.LMSInitialize = function(param) {
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
return "false";
}
// some more functions, omitted.
}
var API = new Scorm_API_12();
I also hit this error. In my case the root cause was async related (during a codebase refactor): An asynchronous function that builds the object to which the "not a function" function belongs was not awaited, and the subsequent attempt to invoke the function throws the error, example below:
const car = carFactory.getCar();
car.drive() //throws TypeError: drive is not a function
The fix was:
const car = await carFactory.getCar();
car.drive()
Posting this incase it helps anyone else facing this error.
In addition to the popular answers above, if you are using a services or helper functions file and doing an export on the functions that you will later import in your project.
Make sure that the function name you are importing matches the exact name of the function being exported from the services, helper, or utils file - and that the function actually exists in the right file! I got stuck on this error and was debugging for a few hours, getting nowhere until I found this out.
Had the same issue on Next.js. On _app.tsx I forgot to wrap the Component with the AuthProvider where I had all the Authentication functions.
In my case after a ton of stackoverflowing I saw what a function thing would go with here... it was merely a silly typo , I forgot to put $ in start of the next line's instruction:
function o_chir(id,nom) {
_id_ochirish = id
_nom= nom
//here it threw that "Uncaught TypeError: nom is not a function"
('#nom').val(s_)
$('#o_chir').modal('show')
}
and PHPStorm didnt give any warning
I received this error when I copied a class object incorrectly using JSON.parse and JSON.stringify() which removed the function like:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log('area of square: ', square.calcArea());
const squareCopy = JSON.parse(JSON.stringify(square));
// Will throw an exception since calcArea() is no longer function
console.log('area of square copy: ', squareCopy.calcArea());
I have the following JavaScript function which receives coordinates and returns the nearest tube station:
function coord() {
var metro = new YMaps.Metro.Closest(new YMaps.GeoPoint(<?=getCoords($addr) ?>), { results : 1 } )
YMaps.Events.observe(metro, metro.Events.Load, function (metro) {
if (metro.length()) {
metro.setStyle("default#greenSmallPoint");
var firstStation = metro.get(0);
var tubest = (firstStation.text).split("метро ");
var tube = tubest[1];
if($("span#tubest").text() == '') {
$('.whiteover').hide();
}
} else {
if($("span#tubest").text() == '') {
$('.whiteover').hide();
}
}
});
}
The value which I need to output as a result of this function execution is the value of the "tube" variable (var tube = tubest[1];). Basically a simple document.write will work. Or a simple return value like:
var tubestation = coord();
However I'm not sure how to achieve this.
You can't have this function return the value, since you're using an observer pattern - which sets up an asynchronous logic to the code. Simply saying, at the time that your coord() function returns, the value is not there yet.
To deal with this, normally you would pass a callback function, then resume your computation there.
Declare your function as:
function coord(callback)
then, after you know the value you want, call the callback with the value:
callback.call(null, tube);
Do it after your if { ... } else { ... } so your callback gets called both on success and on failure (on failure it will pass undefined, you might want to correct it by declaring var tube = null before the if).
then, instead of:
tubestation = coord();
call it like this:
coord(function(tubestation) {
// continuation of your code here
});
You probably won't be able to use document.write since the time to use it would be long past, but you can set the value as the contents of an element that you already generated. You have jQuery in your tags, so it's quite easy:
coord(function(tubestation) {
$('#tube_station').text(tubestation);
});
assuming you have <div id="tube_station"/> somewhere in your HTML.
How about this simple add to that function?
function coord() {
var metro = new YMaps.Metro.Closest(new YMaps.GeoPoint(<?=getCoords($addr) ?>), { results : 1 } )
YMaps.Events.observe(metro, metro.Events.Load, function (metro) {
if (metro.length()) {
metro.setStyle("default#greenSmallPoint");
var firstStation = metro.get(0);
var tubest = (firstStation.text).split("метро ");
var tube = tubest[1];
$('div#myDivResult').html(tube)
if($("span#tubest").text() == '') {
$('.whiteover').hide();
}
} else {
if($("span#tubest").text() == '') {
$('.whiteover').hide();
}
}
});
}