I started using jslib plugins with unity and following the documentation I created this jslib file:
var plugin = {
Autologin: function () {
var sessionData = [
sessionStorage.getItem("USERNAME"),
sessionStorage.getItem("PASSWORD")
];
return sessionData;
},
GetTargetParams: function () {
function findGetParameter(parameterName) {
var result = null,
tmp = [];
var items = location.search.substr(1).split("&");
for (var index = 0; index < items.length; index++) {
tmp = items[index].split("=");
if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]);
}
return result;
}
return findGetParameter("platform");
},
};
mergeInto(LibraryManager.library, plugin);
I use the jslib functions in the c# file like that:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using UnityEngine.UIElements;
using UnityEngine.SceneManagement;
using System;
using System.Runtime.InteropServices;
public class InitHandler : MonoBehaviour
{
public bool isMobile = false;
[DllImport("__Internal")]
private static extern string[] Autologin();
[DllImport("__Internal")]
private static extern string GetTargetParams();
// Start is called before the first frame update
void Start()
{
try
{
switch (GetTargetParams())
{
case "mobile":
isMobile = true;
break;
case "pc":
isMobile = false;
break;
}
}
catch { Debug.LogWarning("Target checking will not be executed cause it's not a webgl build."); }
try
{
StartCoroutine(StartAUTOLogin(Autologin()[0], Autologin()[1]));
}
catch { Debug.LogWarning("Autologin will not be executed cause it's not a webgl build."); }
SceneManager.LoadScene("Login");
}
// Update is called once per frame
void Update()
{
}
IEnumerator StartAUTOLogin(string User, string Password)
{
WWWForm form = new WWWForm();
form.AddField("Username", User);
form.AddField("Password", Password);
}
}
When I build this i don't get ANY compiler errors. My question is now why the functions I import don't work in a webgl build. So when I pass the parameter platform=mobile it's not setting isMobile to true. I'm beginner with jslibs so don't be hard please and don't critisize my english. I'm not native speaker.
Hope you can help me,
Franz Fischer
Related
I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possible be up to 200 or more images being loaded I am try to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also see some lag as images are still getting load while scrolling up and down the list. Also when I scroll back to the top of the list place holders that previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.
When I scroll back to the song list and then back to the artist list the view is reset but it still has a lot of delay when going into the artist tab.
This image is what the screen looks like when first entering the Artist tab.
This image is what the screen looks like after scrolling to the bottom of the list and back to the top.
As you can see the <unknow. and AJR have had their default image replaced.
Here is my code that I am calling to load the images from Wikipedia.
#Override
public void onBindViewHolder(#NonNull ARV holder, int position) {
Artist artist = artistList.get(position);
if(artist!=null) {
holder.artistName.setText(artist.artistName);
String bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
ImageLoader.getInstance().displayImage(imageUrl, holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(true).build());
} catch (IOException e) {
e.printStackTrace();
}
/*ImageLoader.getInstance().displayImage(getCoverArtPath(context,artist.id),holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(false).build());*/
}
}
private StringBuilder getUrlSource(String site) throws IOException {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL localUrl = null;
localUrl = new URL(site);
URLConnection conn = localUrl.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line = "";
String html;
StringBuilder ma = new StringBuilder();
while ((line = reader.readLine()) != null) {
ma.append(line);
Log.i(ContentValues.TAG, "StringBuilder " + ma);
}
Log.i(ContentValues.TAG, "Final StringBuilder " + ma);
return ma;
}
public static String cutImg(StringBuilder split){
int start=split.indexOf("\"source\":")+new String("\"source\":\"").length();
split.delete(0, start);
split.delete(split.indexOf("\""), split.length());
Log.i(ContentValues.TAG, "StringBuilder " + split);
return split.toString();
}
Here is the code that is call the Artist Fragment.
public class ArtistFragment extends Fragment {
int spanCount = 3; // 2 columns
int spacing = 20; // 20px
boolean includeEdge = true;
private RecyclerView recyclerView;
private ArtistAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_artist, container, false);
recyclerView = view.findViewById(R.id.artistFragment);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
Thread t = new Thread()
{
public void run()
{
// put whatever code you want to run inside the thread here.
new LoadData().execute("");
}
};
t.start();
return view;
}
public class LoadData extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
if(getActivity()!=null) {
adapter=new ArtistAdapter(getActivity(),new ArtistLoader().artistList(getActivity()));
}
return "Executed";
}
#Override
protected void onPostExecute(String s) {
recyclerView.setAdapter(adapter);
if(getActivity()!=null) {
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
}
}
I have also tried this using Picasso using the following code:
bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
Picasso.get().load(imageUrl).placeholder(R.drawable.album)
.error(R.drawable.artistdefault).into(holder.artistImage);
} catch (IOException e) {
e.printStackTrace();
}
The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been try for several days to fix this, I have tried several different examples that I found on Stack overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly.
Thanks in advance.
ArtistFragmentconverted to Kotlin
class ArtistFragment : Fragment() {
var spanCount = 3 // 2 columns
var spacing = 20 // 20px
var includeEdge = true
var retrofit: Retrofit? = null
var wikiService: WikiService? = null
var adapter: ArtistAdapter? = null
private var recyclerView: RecyclerView? = null
private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var progress_view: ProgressBar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_artist, container, false)
recyclerView = view.findViewById(R.id.artistFragment)
recyclerView?.setLayoutManager(GridLayoutManager(activity, 3))
progress_view = view.findViewById(R.id.progress_view)
initWikiService()
initList()
//LoadData().execute("")
return view
}
override fun onDestroy() {
super.onDestroy()
viewModelJob.cancel()
}
private fun initList() {
recyclerView!!.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
adapter = ArtistAdapter(this)
adapter?.items = ArrayList()
adapter?.listener = this
recyclerView?.adapter = adapter
viewModelScope.launch {
progress_view.visibility = View.VISIBLE
val wikiPages = getWikiPages()
adapter?.items = wikiPages
progress_view?.visibility = View.GONE
}
}
private suspend fun getWikiPages(): ArrayList<Artist> {
val newItems = ArrayList<Artist>()
withContext(Dispatchers.IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
}
}
}
}
return newItems
}
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
I believe I have resolved most of the issues I was previously seeing I am now down to the following problems:
Artist.item.map { artist -> - Not sure how this should be called, Unresolved reference: item
}.awaitAll().forEach { response -> = forEach is telling me Overload resolution ambiguity. All these functions match.
public inline fun Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) → Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) → Unit): Unit defined in kotlin.collections
newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.
I have reworked the ArtistAdapter not sure if it is correct though.
class ArtistAdapter(private val context: ArtistFragment, private val artistList: List<Artist>?) : RecyclerView.Adapter<ArtistAdapter.ARV>() {
private var dimension: Int = 64
init {
val density = context.resources.displayMetrics.density
dimension = (density * 64).toInt()
hasStableIds()
}
var items: MutableList<Artist> = ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
var listener: Listener? = null
interface Listener {
fun onItemClicked(item: Artist)
abstract fun ArtistAdapter(context: ArtistFragment): ArtistAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ARV {
return ARV(LayoutInflater.from(parent.context).inflate(R.layout.artist_gride_item, parent,
false))
}
override fun onBindViewHolder(holder: ARV, position: Int) {
holder.onBind(getItem(position))
}
private fun getItem(position: Int): Artist = items[position]
override fun getItemId(position: Int): Long = items[position].id
override fun getItemCount(): Int {
return artistList?.size ?: 0
}
inner class ARV(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val artistNameView: TextView = itemView.findViewById(R.id.artistName)
private val artistAlbumArtView: SquareCellView = itemView.findViewById(R.id.artistAlbumArt)
fun onBind(item: Artist) {
artistNameView.text=item.artistName
if(item.artistURL!=null) {
Picasso.get()
.load(item.artistURL)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.artistdefualt)
.into(artistAlbumArtView)
} else {
artistAlbumArtView.setImageResource(R.drawable.artistdefualt)
}
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val artistId = artistList!![bindingAdapterPosition].id
val fragmentManager = (context as AppCompatActivity).supportFragmentManager
val transaction = fragmentManager.beginTransaction()
val fragment: Fragment
transaction.setCustomAnimations(R.anim.layout_fad_in, R.anim.layout_fad_out,
R.anim.layout_fad_in, R.anim.layout_fad_out)
fragment = ArtistDetailsFragment.newInstance(artistId)
transaction.hide(context.supportFragmentManager
.findFragmentById(R.id.main_container)!!)
transaction.add(R.id.main_container, fragment)
transaction.addToBackStack(null).commit()
}
}
}
Logcat Snippet
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or it
s super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
at okhttp3.internal.Util.<clinit>(Util.java:87)
at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
at okhttp3.HttpUrl.get(HttpUrl.java:917)
at retrofit2.Retrofit$Builder.baseUrl(Retrofit.java:506)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.initWikiService(ArtistFragment.kt:103)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.onCreateView(ArtistFragment.kt:43)
It looks to me as a known bug with Picasso.
Try to load default image manually so it won't be replaced with cached one.
Update 14.10.20:
I think the main problem is that you load network content in adapter in rather ineffective way. I suggest to form a list of all urls at first, leaving only image load in adapter.
Also reccomend you to use rerofit2 for network calls and something for async work instead of AsyncTask: rxJava, courutines, flow etc.
I created a sample project to load data async using retrofit2+coroutines.
In activity:
private val viewModelScope = CoroutineScope(Dispatchers.Main)
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
private fun initList() {
viewModelScope.launch {
val wikiPages = getWikiPages()
adapter?.items = wikiPages
}
}
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private suspend fun getWikiPages(): ArrayList<Item> {
val newItems = ArrayList<Item>()
withContext(IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Item(id, title, url))
}
}
}
}
return newItems
}
In viewHolder:
fun onBind(item: Item) {
if (item.url != null) {
Picasso.get()
.load(item.url)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.ic_baseline_broken_image_24)
.into(pictureView)
} else {
pictureView.setImageResource(R.drawable.ic_baseline_image_24)
}
}
In adapter: add hasStableIds() to constructor and override getItemId method:
init {
hasStableIds()
}
override fun getItemId(position: Int): Long = items[position].id
Retrofit Service:
interface WikiService {
#GET("/w/api.php?action=query&prop=pageimages&format=json&pithumbsize=250")
suspend fun getWikiData(#Query("titles") band: String): Response<WikipediaResponse?>
}
I've the below JavaScript code, that returned a function callback related to the user command, the user command could be used in different ways, hence RegEx is required:
(function (undefined) {
"use strict";
var root = this;
var commandsList = [];
var debugStyle = 'font-weight: bold; color: #00f;';
// The command matching code is a modified version of Backbone.Router by Jeremy Ashkenas, under the MIT license.
var optionalParam = /\s*\((.*?)\)\s*/g;
var optionalRegex = /(\(\?:[^)]+\))\?/g;
var namedParam = /(\(\?)?:\w+/g;
var splatParam = /\*\w+/g;
var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#]/g;
var commandToRegExp = function(command) {
command = command.replace(escapeRegExp, '\\$&')
.replace(optionalParam, '(?:$1)?')
.replace(namedParam, function(match, optional) {
return optional ? match : '([^\\s]+)';
})
.replace(splatParam, '(.*?)')
.replace(optionalRegex, '\\s*$1?\\s*');
return new RegExp('^' + command + '$', 'i');
};
var registerCommand = function(command, cb, phrase) {
commandsList.push({ command: command, callback: cb, originalPhrase: phrase });
root.console.log('Command successfully loaded: %c'+phrase, debugStyle);
};
root.fonixListen = {
addCommands: function(commands) {
var cb;
for (var phrase in commands) {
if (commands.hasOwnProperty(phrase)) {
cb = root[commands[phrase]] || commands[phrase];
if (typeof cb === 'function') {
// convert command to regex then register the command
registerCommand(commandToRegExp(phrase), cb, phrase);
} else if (typeof cb === 'object' && cb.regexp instanceof RegExp) {
// register the command
registerCommand(new RegExp(cb.regexp.source, 'i'), cb.callback, phrase);
}
}
}
},
executeCommand: function(commandText) {
for (var j = 0, l = commandsList.length; j < l; j++) {
var result = commandsList[j].command.exec(commandText);
if (result) {
var parameters = result.slice(1);
// execute the matched command
commandsList[j].callback.apply(this, parameters);
return true;
}
}
}
};
}).call(this)
Below are some commands:
var commands = {
'hello :name *term': function(name) {
alert('hello '+name+''); // i.e. consider *term as optional input
},
'items identification': {
'regexp': /^(What is|What's|Could you please tell me|Could you please give me) the meaning of (TF|FFS|SF|SHF|FF|Tube Film|Shrink Film|Stretch Hood|Stretch Hood Film|Flat Film)$/,
'callback': itemsIdentification,
},
'ML SoH': {
'regexp': /^(What is|What's|Could you please tell me|Could you please give me) the (stock|inventory) of ML$/,
'callback': mlSOH,
},
'Report stock on hand': {
'regexp': /^(What is|What's) (our|the) (stock|inventory|SoH) of (TF|FFS|SF|SHF|FF|Tube Film|Shrink Film|Stretch Hood|Stretch Hood Film|Flat Film)$/,
'callback': SoH,
},
'Basic Mathematical Opertions': {
// ?\s? can be used instead of space, also could use /i instead of $/,
'regexp': /^(What is|What's|Calculate|How much is) ([\w.]+) (\+|and|plus|\-|less|minus|\*|\x|by|multiplied by|\/|over|divided by) ([\w.]+)$/,
'callback': math,
},
};
At running the app, the addCommands command is executed, and based on the input command from the user, the executeCommand command is executed.
The above works very fine with me, but I'm moving to C#, and very new to it, so looking for help, at least guiding of some functionalities and tools in C# that can help me write something similar to the above.
UPDATE
More details about what I try to do, actually I've a form, where the user input his command by voice using HTL5 voice API, the API convert this voice into text, then this text i submitted to my app, where my app work start by looking into this text, trying to find the required command using the ReqEx, then execute the programmed function/callback that is mapped with this input command.
I found the solution using Dictionary at using System.Collections.Generic; and using RegEx at using System.Text.RegularExpressions; along with the need of a function called FirstOrDefault availabe at using System.Linq;
I used Action instead of function Func as the callbacks in my case are do not return anything, i.e. they are a void functions, and because no input parameters provided, I used Action-delegate, and did not use Action<string[]>.
The working code is:
using System;
using System.Collections.Generic; // for Dictionary
using System.Linq; // for FirstOrDefault
using System.Text.RegularExpressions; // for RegEx
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var input = "What's the meaning of Stretch Hood";
var functions = new Dictionary<Regex, Action>
{
{new Regex("/^(What is|What's|Could you please tell me|Could you please give me) the meaning of (TF|FFS|SF|SHF|FF|Tube Film|Shrink Film|Stretch Hood|Stretch Hood Film|Flat Film)$/"),
itemsIdentification},
{new Regex("/^(What is|What's|Could you please tell me|Could you please give me) the (stock|inventory) of ML$/"),
mlSOH},
{new Regex("/^(What is|What's) (our|the) (stock|inventory|SoH) of (TF|FFS|SF|SHF|FF|Tube Film|Shrink Film|Stretch Hood|Stretch Hood Film|Flat Film)$/"),
SoH},
{new Regex("/^(What is|What's|Calculate|How much is) ([\w.]+) (\+|and|plus|\-|less|minus|\*|\x|by|multiplied by|\/|over|divided by) ([\w.]+)$/"),
math},
};
functions.FirstOrDefault(f => f.Key.IsMatch(input)).Value?.Invoke(); // This will execute the first Action found wherever the input matching the RegEx, the ?. means if not null ([Null-conditional Operators][1])
// or
Action action;
action = functions.FirstOrDefault(f => f.Key.IsMatch(input)).Value;
if (action != null)
{
action.Invoke();
}
else
{
// No function with that name
}
}
public static void itemsIdentification()
{
Console.WriteLine("Fn 1");
}
public static void mlSOH()
{
Console.WriteLine("Fn 2");
}
public static void SoH()
{
}
public static void math()
{
}
}
}
What I am trying to do is take an array within an object within an array within an object (I know my data structure is ridiculous, any help with that would be great also) and convert the last array into an object with a "key":"value". I'm using Angular 1.5.7 if there is anything in Angular that could help do this. I know that probably makes no sense. I couldn't figure out a way to say what I am trying to do clearly so let me show you:
I start with an object like this:
{"instructor":[{"instructor_emails":[ "test#test.com","tester#tester.com"]}]}
And I want it to be:
{"instructor":[{"instructor_emails":{ "email":"test#test.com","email":"tester#tester.com"}}]}
I tried a couple of things and the closest I found was:
instructor.instructor_emails.map(function(e) {
return { email: e };
});
But it doesn't quite do what I'm trying to do... Any thoughts?
This was correct all along (Thanks Alex)
instructor.instructor_emails.map(function(e) {
return { email: e };
});
Returns:
{instructor:[instructor_emails[{"email":"example1#example1.com",{"email":"example1#example1.com"}]]}
The data structure is still ridiculous but it will suffice for what I am trying to do
You should read up on Object-oriented programming for optimal data storage, particularly concerning classes. To transition between traditional OOP languages, like Java, to JavaScript you can use TypeScript.
Below is a snippet i created using TypeScript:
/// <reference path="definitions/jquery.d.ts" />
console.clear();
var Instructor = (function () {
function Instructor(name, emails) {
if (name === void 0) { name = ""; }
if (emails === void 0) { emails = []; }
this.name = name;
this.emails = emails;
}
Instructor.prototype.addEmail = function (email) {
if (email === void 0) { email = ""; }
//Run validation
if (email.length > 3) {
this.emails.push(email);
}
};
Instructor.prototype.getEmails = function (type) {
if (type === void 0) { type = "array"; }
var self = this;
type = type.toLowerCase();
var getEmails = {
string: function () {
return self.emails.join(" ");
},
object: function () {
return self.emails.map(function (e) {
return { email: e };
});
}
};
if (getEmails[type] === void 0) {
return this.emails;
}
else {
return getEmails[type]();
}
};
return Instructor;
}());
var instructors = [
new Instructor("Michael Bennet I", ["test#test.com", "tester#tester.com"]),
new Instructor("Michael Bennet II", ["test#test.com", "tester#tester.com"]),
];
console.log('array', instructors[0].getEmails());
console.log('object', instructors[0].getEmails("object"));
console.log('string', instructors[0].getEmails("String"));
/*
// This is TypeScript
class Instructor {
constructor(public name: string = "", public emails: string[] = []) {
}
public addEmail(email: string = "") {
//Run validation
if (email.length > 3) {
this.emails.push(email);
}
}
public getEmails(type: string = "array") {
var self = this;
type = type.toLowerCase();
var getEmails = {
string: function () {
return self.emails.join(" ");
},
object: function () {
return self.emails.map(function (e) {
return { email: e };
});
}
}
if (getEmails[type] === void 0) {
return this.emails;
} else {
return getEmails[type]();
}
}
}
var instructors: Instructor[] = [
new Instructor("Michael Bennet I", ["test#test.com", "tester#tester.com"]),
new Instructor("Michael Bennet II", ["test#test.com", "tester#tester.com"]),
];
console.log('array',instructors[0].getEmails());
console.log('object',instructors[0].getEmails("object"));
console.log('string',instructors[0].getEmails("String"));
<p>Object can have their own functions to "get" data they contain in unique ways.</p>
<p>This way, you can get the same data in several different ways</p>
I have a simple SignalR proxy with a single client method on it. The javascript looks like the following:
var proxy = $.connection.orderStatusUpdateEmitter;
proxy.client.onOrderUpdated = function(order){
try {
//This is only hit sometimes
getCustomerOrders(userId)
} catch (e) {
}
}
proxy.connection.start().done(function(c){
$log.info('Connected to order status update emitter');
});
proxy.connection.disconnected = function(data){
$log.info('disconnected');
setTimeout(function() {
proxy.connection.start();
},20000);
}
It seems like there is some type of race condition or I am doing this incorrectly because the handler for onOrderUpdated is not consistently hit. Anything glaringly wrong with the implementation above?
There is nothing special happening in my hub on the server, it looks like the following:
[AuthorizeClaims]
public class OrderStatusUpdateEmitter : Hub
{
private static string _groupIdentifier = "OrderStatusUpdate";
public override Task OnConnected()
{
var identity = Context.Request.Environment["user.identity"] as AuthenticatedUserIdentity;
Groups.Add(Context.ConnectionId, string.Format("{0}-{1}", _groupIdentifier, identity.UserId));
return base.OnConnected();
}
public override Task OnReconnected()
{
var identity = Context.Request.Environment["user.identity"] as AuthenticatedUserIdentity;
Groups.Add(Context.ConnectionId, string.Format("{0}-{1}", _groupIdentifier, identity.UserId));
return base.OnReconnected();
}
public static string GetGroupIdentifier()
{
return _groupIdentifier;
}
public Order OnOrderUpdate(Order order)
{
Clients.Group(String.Format("{0}-{1}",GetGroupIdentifier(),
order.CustomerId)).onOrderUpdated(obj);
}
}
Try the following instead of Group
list is consisting of users with ',' separator.
string[] group = list.Split(',');
for(int i=0; i < group.length; i++)
Clients.User(group[i]).onOrderUpdated(obj);
Exist stringByEvaluatingJavaScriptFromString method in Android like Iphone?.
Not with a simple code like this javascript:wave(). But with a complex Java Script function.
Thanks
You can use loadUrl instead,eg:
webView.loadUrl("javascript:PerformSimpleCalculation()");
It's effect similar to stringByEvaluatingJavaScriptFromString.The API description is not clear.
stringByEvaluatingJavaScriptFromString is a private method in Android API. But it is really useful.
You could retrive this API via Java reflection:
Method stringByEvaluatingJavaScriptFromString , sendMessageMethod;;
Object webViewCore , browserFrame;
private boolean hasIntercepted = true;
Object webViewObject = this;
Class webViewClass = WebView.class;
try {
Field mp = webViewClass.getDeclaredField("mProvider");
mp.setAccessible(true);
webViewObject = mp.get(this);
webViewClass = webViewObject.getClass();
Field wc = webViewClass.getDeclaredField("mWebViewCore");
wc.setAccessible(true);
webViewCore = wc.get(webViewObject);
if (webViewCore != null) {
sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
sendMessageMethod.setAccessible(true);
Field bf= webViewCore.getClass().getDeclaredField("mBrowserFrame");
bf.setAccessible(true);
browserFrame = bf.get(webViewCore);
stringByEvaluatingJavaScriptFromString = browserFrame.getClass().getDeclaredMethod("stringByEvaluatingJavaScriptFromString", String.class);
stringByEvaluatingJavaScriptFromString.setAccessible(true);
}
hasIntercepted = true;
} catch (Throwable e) {
hasIntercepted = false;
}
Then to use this method:
Object argument =(Object) ((String)"window.hitTest("+event.getX()+ ","+event.getY()+")");
try {
Object _tagName = stringByEvaluatingJavaScriptFromString.invoke(browserFrame, argument);
String tagName =(String)_tagName;
} catch (Exception e) {
e.printStackTrace();
}