Access Javascript objects imported by url in html - scala.js

I am writing an embedded application where i am using Office 365 library to access outlook email context which is exposed to my application via global object Office
i wrote already javascript application in which i included script url in html page like this :
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
after i am accessing context in ES6 javascript function like below :
Office.initialize = function (reason) {
$(document).ready(function () {
Office.context.mailbox.addHandlerAsync(Office.EventType.ItemChanged, callbackfunction);
});
};
I want to do the same thing in scala JS.
For that included Office js library in html as above way.
After trying to access Office object like :
#js.native
#JSGlobal
object Office extends js.Any {
def initialize(f: String => Unit):js.Any = js.native
}
when i call this Office object, it is throwing error.
def callback = (reason:String) => {
println(s"reason called in callback function => $reason")
}
Office.initialize(callback)
How to instantiate and access office object in scala JS?
ERROR :
VM3981 playground-fastopt-bundle.js:4078 Uncaught TypeError: $g.Office.initialize is not a function
at HTMLDocument.<anonymous> (VM3981 playground-fastopt-bundle.js:4078)
at mightThrow (VM4209 playground-fastopt-bundle.js:25770)
at process (VM4209 playground-fastopt-bundle.js:25838)
Initialize is not available in Office object. we have to load the function on run time.
Error message :
add your initialization code on the Office.initialize function.
gist of code :
https://gist.github.com/rajeevprasanna/8d4f193bc328f2c2d48e113960fb25a6

Given the error, it seems that the Office.initialize function has not been created yet at the time you try to call it. The Office object exists, but it has no field initialize (or it is not a function).
This probably just because the order of your script tags is not correct. Make sure that the script that is supposed to define Office.initialize is executed before the Scala.js code.

I solved this problem in below approach :
as per error message add your initialization code on the Office.initialize function, i have to define a property to load the function.
#js.native
#JSGlobal
object Office extends js.Object {
var initialize:js.Function1[String, _] = js.native
}
after loaded above function :
Office.initialize = { r:String =>
println(s"function initialized. reason => $r")
}
this approach is similar to event handling of button clicks.
Refer : https://github.com/scala-js/scala-js-dom/blob/5adf7290a4b1fdf7759dfed120e4050f87d9f0a2/src/main/scala/org/scalajs/dom/raw/Html.scala#L416

I am not sure
Office.initialize = { r:String =>
println(s"function initialized. reason => $r")
}
the code is valid or not But I wrote a small example and It works well.
Have a look at https://gist.github.com/abdheshkumar/f2ef0c73b942a8f5a48ec20559679105

Related

#JSGlobalScope in scala.js 1.0 (JavaScriptException, ReferenceError, var is not defined)

After migrating from scala.js 0.6.x to 1.0, I've got some code related to #JSGlobalScope broken.
My use case is like this:
there's a 3rd-party library that expects some global var to be set to a function
when loaded and ready, it calls this function (by name)
I set this function in global scope from scala.js
The code looks like this:
#js.native
#JSGlobalScope
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
then I set this var like this:
Globals.callbackFunctionFor3rdPartyLib = () => {
// do things
}
and then I add the script into the DOM.
This was working with scala.js 0.6.x, but with 1.0 I'm getting an exception like the following:
scala.scalajs.js.JavaScriptException: ReferenceError: callbackFunctionFor3rdPartyLib is not defined
In the changelog for 1.0.0 there's a "Breaking changes" section that mentions this:
Accessing a member that is not declared causes a ReferenceError to be thrown
...
js.Dynamic.global.globalVarThatDoesNotExist = 42
would previously create said global variable. In Scala.js 1.x, it also throws a ReferenceError.
My question is:
what is the right way to do something like this (create a new global var) in scala.js 1.0?
If you know you'll always be in a browser context, you can use #JSGlobal("window") instead of #JSGlobalScope on your Globals, which will then be equivalent to doing window.myGlobalVarFor3rdPartyLib in JS. So that will work.
#js.native
#JSGlobal("window")
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
If not, but you are using a script (so not a CommonJS nor an ES module), the best thing is actually to use
object Globals {
#JSExportTopLevel("myGlobalVarFor3rdPartyLib")
var foo: js.Function[Unit] = ...
}
Note that Globals is a normal Scala object now, not a JS one.
The #JSExportTopLevel creates a top-level var myGlobalVarFor3rdPartyLib at the top of the script, and then assigning Globals.foo will also assign that top-level var.
If you're not using a script nor know that you're going to always be in a browser, then you need to figure out the global object yourself. Scala.js 0.6.x tried to do that for you, but could fail, so we don't do that anymore. You can at least follow the "instructions" on the documentation of js.special.fileLevelThis to reproduce what Scala.js 0.6.x was doing. I repeat the instructions here:
Using this value should be rare, and mostly limited to writing code
detecting what the global object is. For example, a typical detection
code--in case we do not need to worry of ES modules--looks like:
val globalObject = {
import js.Dynamic.{global => g}
if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
// Node.js environment detected
g.global
} else {
// In all other well-known environment, we can use the global `this`
js.special.fileLevelThis
}
}
Note that the above code is not comprehensive, as there can be JavaScript
environments where the global object cannot be fetched neither through
global nor this. If your code needs to run in such an environment, it
is up to you to use an appropriate detection procedure.

Get newly created id of a record before redirecting page

I would like to retrieve the id of a newly created record using javascript when I click on save button and just before redirecting page.
Do you have any idea please ?
Thank you !
One way to do this in Sugar 7 would be by overriding the CreateView.
Here an example of a CustomCreateView that outputs the new id in an alert-message after a new Account was successfully created, but before Sugar gets to react to the created record.
custom/modules/Accounts/clients/base/views/create/create.js:
({
extendsFrom: 'CreateView',
// This initialize function override does nothing except log to console,
// so that you can see that your custom view has been loaded.
// You can remove this function entirely. Sugar will default to CreateView's initialize then.
initialize: function(options) {
this._super('initialize', [options]);
console.log('Custom create view initialized.');
},
// saveModel is the function used to save the new record, let's override it.
// Parameters 'success' and 'error' are functions/callbacks.
// (based on clients/base/views/create/create.js)
saveModel: function(success, error) {
// Let's inject our own code into the success callback.
var custom_success = function() {
// Execute our custom code and forward all callback arguments, in case you want to use them.
this.customCodeOnCreate(arguments)
// Execute the original callback (which will show the message and redirect etc.)
success(arguments);
};
// Make sure that the "this" variable will be set to _this_ view when our custom function is called via callback.
custom_success = _.bind(custom_success , this);
// Let's call the original saveModel with our custom callback.
this._super('saveModel', [custom_success, error]);
},
// our custom code
customCodeOnCreate: function() {
console.log('customCodeOnCreate() called with these arguments:', arguments);
// Retrieve the id of the model.
var new_id = this.model.get('id');
// do something with id
if (!_.isEmpty(new_id)) {
alert('new id: ' + new_id);
}
}
})
I tested this with the Accounts module of Sugar 7.7.2.1, but it should be possible to implement this for all other sidecar modules within Sugar.
However, this will not work for modules in backward-compatibility mode (those with #bwc in their URL).
Note: If the module in question already has its own Base<ModuleName>CreateView, you probably should extend from <ModuleName>CreateView (no Base) instead of from the default CreateView.
Be aware that this code has a small chance of breaking during Sugar upgrades, e.g. if the default CreateView code receives changes in the saveModel function definition.
Also, if you want to do some further reading on extending views, there is an SugarCRM dev blog post about this topic: https://developer.sugarcrm.com/2014/05/28/extending-view-javascript-in-sugarcrm-7/
I resolved this by using logic hook (after save), for your information, I am using Sugar 6.5 no matter the version of suitecrm.
Thank you !

Ext.define() order

I'm using Extjs5 and Sencha Cmd, and I'm working on a l10n engine (over gettext) to implement localization.
Suppose I want to offer a translation function to every class of my project, named _().
In every controller, view, model and any class, I'd like to be able to write something like that:
Ext.define('FooClass', {
someStrings: [
_('One string to translate'),
_('A second string to translate'),
_('Yet another string to translate')
]
});
First problem: _() must exist before all the Ext.define() of my project are executed. How to achieve that?
Second problem: _() is looking in "catalogs" that are some JavaScript files generated from .po files (gettext). So, those catalogs must have been loaded, before all the Ext.define() of my app are executed.
_() is a synchronous function, it musts immediately return the translated string.
Edit concerning the edited question
You have at least two ways to load External libraries:
Ext.Loader.loadScript
loadScript( options )
Loads the specified script URL and calls the supplied callbacks. If
this method is called before Ext.isReady, the script's load will delay
the transition to ready. This can be used to load arbitrary scripts
that may contain further Ext.require calls.
Parameters
options : Object/String/String[] //The options object or simply the URL(s) to load.
// options params:
url : String //The URL from which to load the script.
onLoad : Function (optional) //The callback to call on successful load.
onError : Function (optional) //The callback to call on failure to load.
scope : Object (optional) //The scope (this) for the supplied callbacks.
If you still run into problems you can force the loader to do a sync loading:
syncLoadScripts: function(options) {
var Loader = Ext.Loader,
syncwas = Loader.syncModeEnabled;
Loader.syncModeEnabled = true;
Loader.loadScripts(options);
Loader.syncModeEnabled = syncwas;
}
Place this in a file right after the ExtJS library and before the generated app.js.
Old Answer
You need to require a class when it is needed, that should solve your problems. If you don't require sencha command/the ExtJS class system cannot know that you need a specific class.
Ext.define('Class1', {
requires: ['Class2'],
items: [
{
xtype: 'combo',
fieldLabel: Class2.method('This is a field label')
}
]
});
For further reading take a look at:
requires
requires : String[]
List of classes that have to be loaded before instantiating this
class. For example:
Ext.define('Mother', {
requires: ['Child'],
giveBirth: function() {
// we can be sure that child class is available.
return new Child();
}
});
uses
uses : String[]
List of optional classes to load together with this class. These
aren't neccessarily loaded before this class is created, but are
guaranteed to be available before Ext.onReady listeners are invoked.
For example:
Ext.define('Mother', {
uses: ['Child'],
giveBirth: function() {
// This code might, or might not work:
// return new Child();
// Instead use Ext.create() to load the class at the spot if not loaded already:
return Ext.create('Child');
}
});
Define the translate function outside the scope of the ExtJs project and include it before the Ext application is included in the index.html.
The scripts are loaded in the right order and the _() function is ready to use in your whole project.
i18n.js
function _() {
// do the translation
}
index.html
<html>
<head>
<script src="i18n.js"></script>
<script id="microloader" type="text/javascript" src="bootstrap.js"></script>
</head>
<body>
</body>
</html>

Creating custom DOM events with scalajs

I can't find a way to create custom events with scala-js. For instance, with js you can create a custom event like the following (taken from here):
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
However, there is no constructor for CustomerEvent or Event in scala-js that accept arguments. Also, subclassing either such as:
class DrawEvent extends Event {
override def `type` = "draw"
}
leads to
Uncaught TypeError: undefined is not a function
when trying to construct via new DrawEvent()
Any ideas?
To instantiate javascript classes in ScalaJs you have to use js.Dynamic.newInstance:
This should work for your use case:
val event = js.Dynamic.newInstance(js.Dynamic.global.CustomEvent)("build", js.Dynamic.literal(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
There is more info available at the remarks portion (all the way at the bottom) of:
http://www.scala-js.org/doc/calling-javascript.html
Here is the same solution using some imports to make it shorter
import js.Dynamic.{ global => g, newInstance => jsnew, literal => lit }
val event = jsnew(g.CustomEvent)("build", lit(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
If you want to stay in the typed DOM (assuming you are talking about the scala-js-dom library), you can do:
new CustomEvent().initCustomEvent('build', false, false, elem.dataset.time)
The constructor you are using is actually only specified in DOM 4 (see MDN).

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.