I am making a simple iOS share extension that will operate on web pages. It is not unlike Pinterest, in that it grabs certain info from the page. But it is different from Pinterest in that the JS used to do the grabbing is sometimes custom to the web page domain.
So in the ExtensionPreprocessingJS run() function I do the following to load and execute the custom JS:
var Action = function() {};
Action.prototype = {
run: function(arguments) {
var e = document.createElement('script');
e.setAttribute('type','application/javascript');
e.setAttribute('id','my-unique-id');
e.setAttribute('src','//mydomain.com/bookmarklet?d='+document.domain);
document.body.appendChild(e);
<some code to extract results and generate JSON>
arguments.completionFunction({ <my JSON here> });
}
};
var ExtensionPreprocessingJS = new Action
My bookmarklet endpoint provides some JS that finds the relevant info from the page for "pinning".
My problem is that this happens asynchronously and not before I pass back the results at the end of the run method using
arguments.completionFunction( <my JSON here> );
I have tried waiting for the result to be set before calling completionFunction() but the extension seems to treat that as completionFunction() not being called at all. That is, it seems the design of the share extension framework doesn't accommodate asynchronous JS. It assumes the result from the JS is available after the run() function returns.
Am I misunderstanding the share extension design, or do I need to look for a different way to do this? I do notice that Instapaper created a share extension with no native UI and I'm wondering whether this is why they did so.
Related
I am building an angular application which is running as a web resource on Dynamics 2013.
The application runs using a button which is added to the commandContainer using Ribbon workbench which then that button calls a Xrm.Internal.openDialog
All this works fine until I want to start using the Entities exposed by Xrm.Page.Data
Basically my button runs in the context of the main page of dynamics however the Entities are inside an iframe which based on the page I am in has a different Id and name.
So using a simple selector I can not get its contentWindow and start using the Entities.
The #crmContentPanel always has few iframes in it starting from #contentIFrame0 to #contentIFrame(n) and I can never know which iframe is the one with Entities in it.
What is the best practice, associated work flow with developing applications in this environment? How can I easily and reliably access the correct frame which holds the main page entities and work with them.
Perhaps the script is in the wrong location and needs to injected into the main content area so it has direct access to the correct Xrm? How can I achieve that?
Furthermore once I eventually manage to access this data, how can I easily pass this data to my angular application which runs in the dialog as from the documentation I read that the dialog is only allowed 1 query string param and it has to be called data. That would not be enough for my application to start using $routeParams. And I don't think using local or session storage is nice practice. What is the correct approach in this situation.
Sample code of my button script:
function runSendSender() {
// Content Iframe Entity data:
var contentFrameXrm = $('#crmContentPanel')
.find("iframe#contentIFrame0...n")[0]
.contentWindow['Xrm'];
// even if above selector was consistent across pages
// I need to send over much more than this one Id :(
var data = contentFrameXrm.Page.data.entity.getId();
var src = "/WebResources/concep_/ConcepDynamicsApp/ConcepDynamicsApp.html?data=" + data;
var DialogOptions = new Xrm.DialogOptions();
DialogOptions.width = 800;
DialogOptions.height = 500;
Xrm.Internal.openDialog(src, DialogOptions, null, null, CallbackFunction);
function CallbackFunction(returnValue) { }
}
Little more detail
When I type the following in the console I can sometimes (randomly) read the title of the form:
$('#crmContentPanel').find("iframe#contentIFrame0")[0].contentWindow['Xrm'].Page.ui.get_formTitle();
But the same code from the associated web resource function can not access the iframe and errors:
Can not Cannot read property 'contentWindow' of undefined.
Why is the iframe not accessible via the resource script and how can I access the correct context and form title/id.
I'm usually including following JavaScript file to the header of the custom WebResource that need to have an access to the CRM specific actions / information:
<script src="ClientGlobalContext.js.aspx" type="text/javascript"></script>
This gives access to some none-entity specific information, such as Xrm.Page.context.getServerUrl() or Xrm.Page.context.getUserId() for example.
But if you added layer with your own iFrame on top of the standard entity page, you definitely can access to information underneath your current context by using following construction:
window.parent.Xrm.Page.data.entity.attributes.get("name").getValue();
Note the window.parent prefix.
The record Id can be sent to runSendSender as parameter by the ribbon itself. Just add the appropriate CrmParameter (MSDN) to the function call.
In your case, the parameter value would be FirstPrimaryItemId ("Provides one GUID identifier as a string for the record being viewed.")
After that, you'll have your function changed like this
function runSendSender(recordId) { ... }
Also, stay out from internals: to open a web resource in a dialog, you should use the supported way (link provides info about passing parameters other than data to the resource).
Xrm.Utility.openWebResource(webResourceName,webResourceData,width, height)
I would like to use Readability's javascript to clean up news articles, then download the generated article. What this entails is being able to execute their js code:
window.baseUrl = 'https://www.readability.com';
window.readabilityToken = '';
var s = document.createElement('script');
s.setAttribute('type','text/javascript');
s.setAttribute('charset','UTF-8');
s.setAttribute('src', baseUrl + '/bookmarklet/read.js');
document.documentElement.appendChild(s);
in an environment different from a traditional web browser. For example, this is possible to do by executing the above code on a site using Firebug; I would like to emulate that functionality through Java (without a browser). One of the problems I see here is the "window" object, which may not be present when accessing pages programmatically.
Any hints on whether this is possible and how to implement?
Take a look at a headless browser, such as EnvJS.
I have been reading up on HTML 5 worker threads but all the samples i have seen seem to require the javascript be in its own file.
so im basicly wondering if its posible to start a worker work directly towards a function.
The end goal here being something along the lines of:
function AllJavascriptIsLoaded()
{
if(gWorkersSupported)
{
var Worker = new Worker(MyFunc)
Worker.Start();
}
else
{
// Horrible user experience incomming.
MyFunc();
}
}
function MyFunc()
{
// Complex and time consuming tasks
}
To my knowledge, this is not allowed for security reasons. I'd assume that a child object, or any JS script in the same file, would potentially have access to the parent DOM window, which Web Workers are not allowed to access.
So, we're stuck with posting messages to other files unless someone finds a nicer way to do it ;)
You can use something called inline-worker.
Basically you create a script resource via dataURI or BlobURL for the worker script. Given that the content of the script can be generated, you can use Function.toString() to build the content of the worker.
Example use BlobURL: http://www.html5rocks.com/en/tutorials/workers/basics/
Example use both technique: https://github.com/jussi-kalliokoski/sink.js/blob/master/src/core/inline-worker.js
Jeffrey is right about the security restriction of WebWorker. The code running in the worker cannot access the DOM, so it should only be used for calculation heavy tasks. If you try to access the DOM inside worker's code it would raise an error.
vkThread plugin helps you to implement exactly what you requested.
take a look at http://www.eslinstructor.net/vkthread/
there are examples for different kind of functions: regular function, function with context, with dependencies, anonymous, lambda.
Goal: To create a bookmarklet that calls a remote javascript file that opens a popup window. The popup window is functionally similar to what Delicious's bookmarklet does.
Background: Currently, I'm using window.open within this javascript file, however the popup is getting blocked by pretty much every major browser.
The alternative solution to this is very similar to the way Delicious wrote their bookmarklet - calling window.open through a javascript query within the bookmarklet itself. However, I need the ability to modify the other contents of my javascript file in the future without requiring users to continually grab newest releases of the bookmarklet.
What I've determined to be happening: Since the window.open call is not occurring directly as a result of a click by the user, the browser feels this is something that should be blocked. Here's a source on this.
This is the tutorial I referenced most recently in creating the call to the remote js file.
Here is a basic example of what my code is doing; the window.open/popup portion is the only significant part I'm including as it's the only part I feel is causing the complication:
Example of the remote javascript file:
if (typeof jQuery == 'undefined') {
var jQ = document.createElement('script');
jQ.type = 'text/javascript';
jQ.onload=runthis;
jQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
document.body.appendChild(jQ);
} else {
runthis();
}
function runthis() {
window.open('http://www.google.com/', 'a title',
'location=yes,links=no,scrollbars=no,toolbar=no,width=550,height=550');
}
I'd really appreciate any help as this has been stumping me for a while!
An approach that looks better and side-steps the blocking issue is to have the bookmarklet insert an iframe in the page the user is currently viewing. Ended up taking this approach back when I asked this question. Worked out fine.
In a client-side web application, I would like to:
open an Excel spreadsheet,
export some application data to Excel,
allow the user to work with it, and
when they are done, read the (potentially changed) data back into my application.
I would like the user to have a fluid experience and detect when they are done with excel by hooking up to the BeforeClose event, but I find that I am unable to hook up to Excel's events in javascript/HTML.
function BeforeCloseEventHandler(cancel) {
// TODO: read values from spreadsheet
alert("Closing...");
}
function openExcel() {
var excel = new ActiveXObject("Excel.Application");
var workbook = excel.Workbooks.Add();
var worksheet = workbook.Worksheets(1);
worksheet.Cells(1, 1).Value = "First Cell";
worksheet.Cells(1, 2).Value = "Second Cell";
workbook.BeforeClose = BeforeCloseEventHandler; // THIS DOESN'T WORK
excel.Visible = true;
excel.UserControl = true;
}
Does anyone have any suggestions?
After doing some research, I have discovered that I cannot hook up events to dynamic ActiveX objects (i.e., the ones that are created by the new ActiveXObject constructor) in javascript.
One idea is that I create a wrapper Windows Form user control that would be hosted inside of an <object> tag in the web app. The user control would call Excel and receive events, and raise events back to javascript, which I could hook up to using the <script for="..." event="..."> mechanism. Not sure that this will work, but I will try it.
Even if it does work, I am not particularly happy about this solution. There are too many layers--the javascript is being called from a silverlight control meaning that my data has to cross 3 boundaries there and back: Silverlight -> Javascript -> Hosted Winform User Control -> Excel.
It would be nice to eliminate some of these boundaries.
I think also your second approach using a Windows From control hosted in IE will not work.
IE behaves different as a scripting host. There are certain limitations as a blog post by Eric Lippert mentions:
Implementing Event Handling, Part Two
I don't believe this is possible. The reason being, when you call the following code:
var excel = new ActiveXObject("Excel.Application");
You're actually opening up Excel. So with the following line:
workbook.BeforeClose = BeforeCloseEventHandler;
Its like you're telling the Excel application to run Javascript, which isn't possible. I've tried researching alternatives, like creating an event object, defining the code behind it, then assigning it to workbook.BeforeClose, but I would run into the same problem: Excel events can't be detected by javascript. Mainly because it runs as a seperate process.
So here's some more alternatives you may consider:
Save the Excel data on the user's computer, then when the user loses excel, have them click somewhere that calls your 1st method, which reads that file and displays it.
Read the data from the excel file and then display it.
Don't close your excel object (this will probably leave excel open as a process on your computer), and have a timer event in javascript. Every 5 seconds, check if Excel is still open, and if it is not open, read the file and display it.
Sorry I couldn't be anymore help, and I'm not too sure if any of those alternatives would work, but good luck!