Javascript Observable collection count - javascript

I'm, accessing Silverlight ObservableCollection count in javascript, but I get the following error.,
Microsoft JScript runtime error: System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Windows.Hosting.ScriptingInterface.GetScriptParamValueForType(ScriptParam scriptParam, Type desiredType)
at System.Windows.Hosting.ScriptingInterface.ConvertFromScriptParams(Type[] desiredTypes, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.ListIndexerMember.Invoke(ManagedObject obj, InvokeType invokeType, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.Invoke(ManagedObject obj, InvokeType invokeType, String memberName, ScriptParam[] args)
at System.Windows.Hosting.ManagedHost.InvokeScriptableMember(IntPtr pHandle, Int32 nMemberID, Int32 nInvokeType, Int32 nArgCount, ScriptParam[] pArgs, ScriptParam& pResult, ExceptionInfo& pExcepInfo)
I'm using following code, where children is a observablecollection of custom object.,
reg.OnDropping = function (sender, args) {
if (args.toItem.Children.Count > 0) {
args.cancel = true;
}
else {
args.cancel = false;
}
}
Is there any other way to access the count in Javascript?
Regards,
Karthik

In order to access a property of an object from Javascript either the property needs to be marked with the ScriptableMember attribute or the class to which it belongs is marked as ScriptableType. Neither of these is true of the ObservableCollection<T> class.
A pragmatic solution would be to add a HasChildren property to your Custom object:-
[ScriptableMember]
public bool HasChildren
{
get { return Children.Count > 0; }
}

Have you made that observablecollection ScriptableMember? You need to add this attribute to make in available in javascript. Check the following link for more information:
Walkthrough: Calling Managed Code from JavaScript
HtmlPage.RegisterScriptableObject Method
I hope it helps.

Related

EvaluateJavaScript only allow a return type of String?

I made a common function (in Kotlin) that I could invoke the JavaScript function per the command given.
fun evaluateJsFromNative(command: String,
webView: WebView, function: (value : String) -> Unit ) {
webView.evaluateJavascript("(function() { return $command; })();") {
s -> function(s)
}
}
However, it only allow value String as the return type. If I wanted it to be something more generic, that I could have return type of Boolean, Int, or even nothing, how could I make this even more generic?
E.g. I can't do this... it will error in the function(s) stating the s need to be a String
fun evaluateJsFromNative(command: String,
webView: WebView, function: (value : Boolean) -> Unit ) {
webView.evaluateJavascript("(function() { return $command; })();") {
s -> function(s)
}
}
The error you are getting is because you have defined your function to take a Boolean parameter, but you are passing a String parameter at s -> function(s). It is a String because that is how WebView.evalueateJavascript() is defined.
You cannot change the type of function that webView.evaluateJavascript() accepts. You can, however, convert the String parameter to Boolean and call your function with that.
For example (using your second implementation taking Boolean):
fun evaluateJsFromNative(command: String,
webView: WebView, function: (value : Boolean) -> Unit ) {
webView.evaluateJavascript("(function() { return $command; })();") {
s -> function(s.toBoolean())
}
}
// you would call it like this...
evaluateJsFromNative(mycommand, mywebview) {
Log.d("SomeTag","Value=$it")
}
But this is highly simplified because you could supply it any string as a command, including non-Javascript. The docs say the function parameter of evaluateJavascript() will only be called if the Javascript evaluates to something other than null (note, it will call your function with the value 'null' if your WebView does not have Javascript enabled). So, even if and assuming you pass good Javascript, that returns non-null, s may still not successfully convert to a Boolean. In other words, you have a lot of work to do to handle bad input parameters, and handling types you don't expect.
But you asked about something more generic. So here is a try; you would have to expand the when statement for all the types you support (and of course error handling).
fun <T : Any> evaluateJsFromNative(command: String,
webView: WebView, klass: Class<T>, function: (value : Any?) -> Unit ) {
webView.evaluateJavascript("(function() { return $command; })();") {
s -> if (s.convert(klass) != null) function(s.convert(klass)!!)
}
}
fun <T> String.convert(klass: Class<T>) : Any? {
when (klass.name.toString()) {
"boolean" -> return this.toBoolean()
else -> return null
}
}
// call it like this...
evaluateJsFromNative(mycommand, mywebview, Boolean::class.java) {
Log.d("SomeTag","Value=$it")
}
This example requires that you specify the type you expect from the Javascript, and then you must parse it from a string to the type you expect. And you must pre-define what types you will support. It would be nice if the conversion were simpler, but alas, there isn't this kind of type conversion in Kotlin according to this post in the Kotlin forums
I hope this helps

Access QList object in QML

I have a little problem accessing a QList objects since Javascript. I have a C ++ class that allows me to perform SQL queries since QML / JS. Everything works, I get my results in C ++.
My problem is that I'd returned to QML an QList object.
This is my function in C++ to return SQL result (Note is a simple object with different attributes) :
QList<Note> Storage::setQuery(QString query)
{
QList<Note> noteItems;
QSqlQuery qsqlQuery;
bool ok = qsqlQuery.exec(query);
if(!ok)
{
qDebug() << "Error setQuery" << m_sqlDatabase.lastError();
}
else
{
while (qsqlQuery.next()) {
Note my_note;
QString note = qsqlQuery.value("message").toString();
my_note.setMessage(note);
noteItems.append(my_note);
}
}
return noteItems;
}
But when I call this function from JS I get this error: Unknown method return type: QList<Note>
The problem is the return type, QML JS doesn't know the type QList<Object>, why? What do I do wrong
If you want to use c++ QList as a model in Qml, I would recommend you to use the following procedure. I'm using my own example, you might change it according to your needs.
Storage.h
class Storage : public QObject {
Q_PROPERTY(QQmlListProperty<Note> getList READ getList)
public:
QQmlListProperty<Note> getList();
void setQuery(QString query);
QList<Note> noteItems;;
private:
static void appendList(QQmlListProperty<Note> *property, Note *note);
static Note* cardAt(QQmlListProperty<Note> *property, int index);
static int listSize(QQmlListProperty<Note> *property);
static void clearListPtr(QQmlListProperty<Note> *property);
};
Storage.cpp
void Field::appendList(QQmlListProperty<Card> *property, Note *note) {
Q_UNUSED(property);
Q_UNUSED(note);
}
Note* Field::cardAt(QQmlListProperty<Note> *property, int index) {
return static_cast< QList<Note> *>(property->data)->at(index);
}
int Field::listSize(QQmlListProperty<Note> *property) {
return static_cast< QList<Note> *>(property->data)->size();
}
void Field::clearListPtr(QQmlListProperty<Note> *property) {
return static_cast< QList<Note> *>(property->data)->clear();
}
QQmlListProperty<Note> Field::getList() {
return QQmlListProperty<Note>( this, &list[0], &appendList, &listSize, &cardAt, &clearListPtr );
}
void Storage::setQuery(QString query)
{
QList<Note> noteItems;
QSqlQuery qsqlQuery;
bool ok = qsqlQuery.exec(query);
if(!ok)
{
qDebug() << "Error setQuery" << m_sqlDatabase.lastError();
}
else
{
while (qsqlQuery.next()) {
Note my_note;
QString note = qsqlQuery.value("message").toString();
my_note.setMessage(note);
noteItems.append(my_note);
}
}
}
main.cpp
int main(int argc, char *argv[])
{
qmlRegisterType<Note>();
}
The QQmlListProperty class allows applications to expose list-like properties to QML. To provide a list property, a C++ class must implement the operation callbacks, and then return an appropriate QQmlListProperty value from the property getter. List properties should have no setter. When extending QML with C++ code, a C++ class can be registered with the QML type system to enable the class to be used as a data type within QML code.
Did you register Note as a meta type? Probably that's what's missing:
http://doc.qt.io/qt-5/qmetatype.html#Q_DECLARE_METATYPE
In Qt 4 you'll also have to register QList<Note>, but not in Qt 5.
Oh, and Note should probably be a Q_GADGET, otherwise you cannot access its contents from QML either. Make sure you use Qt 5.5+. Otherwise, you'll need QList<Note*> and make those Note objects inherit from QObject.

Accessing property of NPAPI plugin from Javascript

Have a problem when try to get a value of a property via Javascript using a NPAPI plugin;
During the debugging I see that all the chain of functions (HasProperty, HasMethod, and GetProperty) are called. More over I see that during calling GetProperty I set the new values into the result parameter. But after leaving GetProperty I get an exception and can't understand what the reason of it.
May FireFox call some additional functions which I forgot to initialize?
Thanks in advance
My code is:
// static function which calls Get_Property for the instance of CScriptableNPObject
bool CScriptableNPObject::NP_GetProperty(NPObject *npobj, NPIdentifier name, NPVariant *result)
{
m_Logs.WriteLogs(10, _T("Enter the CScriptableNPObject::NP_GetProperty()"));
return ((CScriptableNPObject *)npobj)->GetProperty(name, result);
}
// just converter name from NPIdentifier to char *
bool CScriptableNPObject::GetProperty(NPIdentifier name, NPVariant *result)
{
NPUTF8 *pszProperty = m_pNPNFuncs->utf8fromidentifier(name);
return GetProperty(pszProperty, result);
}
// checking the dictionary of properties, if property exists put its value into the result
bool CScriptableNPObject::GetProperty(NPUTF8 *pszProperty, NPVariant *result)
{
VOID_TO_NPVARIANT(*result);
JSPropertiesMap::iterator it = m_JSProperties.find(pszProperty);
if (it == m_JSProperties.end())
return false;
NPUTF8 *pszNewPropertyValue = new NPUTF8[it->second->value.stringValue.UTF8Length + 1];
sprintf(pszNewPropertyValue, it->second->value.stringValue.UTF8Characters);
STRINGZ_TO_NPVARIANT(pszNewPropertyValue, *result);
return true;
}
You need to use NPN_MemAlloc():
NPUTF8* newValue = NPN_MemAlloc(length + 1);

Pass data by hostpage error:JavaScriptObject$ cannot be cast to com.pkg.model

I followed this article to use hostpage to pass an array to client:
https://developers.google.com/web-toolkit/articles/dynamic_host_page
Currently,I can see follow content in firebug
<html style="overflow: hidden;">
<head>
......
<script type="text/javascript">
var rcmdFriends=[{"Name":"Friend-0","Image":"url"}];
</script>
</head>
......
</html>
Then I tried to use these code to get js variable(a json array actually) from hostpage and print it to user:
//get array from host page
private native JsArrayExt<People> getRecommendedFriends()/*-{
return $wnd.rcmdFriends;
}-*/;
#Override
public void onModuleLoad()
{
final FlowPanel fPanel = new FlowPanel();
JsArrayExt<People> channels = getRecommendedFriends();
for (int i = 0, len = channels.length(); i < len; i++)
{
//"print" name to user
fPanel.add(new Label(channels.get(i).getName()));
}
RootPanel.get().add(fPanel);
}
//model definition
#SingleJsoImpl(PeopleImpl.class)
public interface People extends HasName
{
String getImage();
void setImage(String Image);
}
But got this eror:
java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$ cannot be cast to com.pkg.People
Strangely,I can already see the length of "channels" is 1,and why do I get this casting error?How to solove this problem?
You cannot cast to an ordinary Java pojo. You must implement an overlay type
public class PersonJSON extends JavaScriptObject {
protected PersonJSON() {
}
public final native String getName() /*-{
return this.Name;
}-*/;
public final native String getImage() /*-{
return this.Image;
}-*/;
}
Then you can call
JsArray<PersonJSON> channels = getRecommendedFriends();
and read out the values from the PersonJSON elements;
Assuming JsArrayExt is the interface from Why can't I define interface for overlay type lightweight collections?, I suppose that the fact you do not use an explicit JSO subclass confuses the DevMode.
Because you directly call a JSNI method, I don't understand why you don't use a JsArrayExtImpl<PersonImpl> which I believe would Just Work™; there's no point in using the interfaces here.
If you really can't make it work, I'd suggest using AutoBeans instead (it unfortunately requires a small serialize/parse dance in DevMode: AutoBeanCodex.decode(factory, Person.class, new JSONObject(rawJso).toString()), whereas in prod mode you can simply use AutoBeanCodex.decode(factory, Person.class, (JsoSplittable) rawJso)). In your case, it'd require another dance because you're using an array as the root object; see GWT Autobean - how to handle lists?

Handling JavaScript exceptions from Silverlight

I'm having trouble adding proper exception handling to existing code that makes heavy use of Silverlight - JavaScript interoperability. In this case, my JavaScript can throw an exception that I want to handle meaningfully in Silverlight.
From Silverlight, I'm creating an instance of a JavaScript object, then later I'm calling a method on that object:
public class MyWrapper
{
dynamic _myJSObject;
public MyWrapper()
{
_myJSObject = HtmlPage.Window.CreateInstance("MyJSObject");
}
public int MyMethod()
{
try
{
int result = (int)_myJSObject.MyMethod();
}
catch (Exception ex)
{
// I want to add meaningful exception handling here
}
}
}
Whenever MyJSObject.MyMethod throws an exception, there are two problems:
The browser shows a message that an exception has occurred.
Information about the exception is not passed to my managed code. Instead I get a RuntimeBinderException which just says "Cannot invoke a non-delegate type" and contains no other information whatsoever. This does not seem to match what is described here; I'd expect an InvalidOperationException.
I've tried avoiding to cast the returned value of the method:
object tmp= _myJSObject.MyMethod();
This makes no difference. Changing the type of exception thrown on the JavaScript side has no effect either.
MyJSObject.prototype.MyMethod = function ()
{
throw "Hello Silverlight!";
}
The only solution I can think of right now is abusing the function's return value to pass information about the exception, but that will make my code a whole lot uglier... so:
Why is the behavior I'm seeing different from what is described in documentation? Does it have to do with my use of dynamic somehow? How can I properly handle exceptions that occur in JavaScript in my managed code?
After quite a bit of experimentation, I concluded that there is no way to directly handle the JavaScript exception from Silverlight. In order to be able to process the exception, the JavaScript code needs to be changed slightly.
Instead of throwing the error, I return it:
function MyMethod()
{
try
{
// Possible exception here
}
catch (ex)
{
return new Error(ex);
}
}
Then on the Silverlight side, I use a wrapper around ScriptObject to turn the return value into an exception again. The key here is the TryInvokeMember method:
public class ScriptObjectWrapper : DynamicObject
{
private ScriptObject _scriptObject;
public ScriptObjectWrapper(ScriptObject scriptObject)
{
_scriptObject = scriptObject;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = _scriptObject.Invoke(binder.Name, args);
ScriptObject s = result as ScriptObject;
if (s != null)
{
// The JavaScript Error object defines name and message properties.
string name = s.GetProperty("name") as string;
string message = s.GetProperty("message") as string;
if (name != null && message != null && name.EndsWith("Error"))
{
// Customize this to throw a more specific exception type
// that also exposed the name property.
throw new Exception(message);
}
}
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
_scriptObject.SetProperty(binder.Name, value);
return true;
}
catch
{
return false;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
try
{
result = _scriptObject.GetProperty(binder.Name);
return true;
}
catch
{
result = null;
return false;
}
}
}
Potentially you could improve this wrapper so it actually injects the JavaScript try-catch mechanism transparently, however in my case I had direct control over the JavaScript source code, so there was no need to do this.
Instead of using the built in JavaScript Error object, it's possible to use your custom objects, as long as the name property ends with Error.
To use the wrapper, the original code would change to:
public MyWrapper()
{
_myJSObject = new ScriptObjectWrapper(
HtmlPage.Window.CreateInstance("MyJSObject"));
}

Categories