Managing resources in a sitelet - javascript

I'm beginning my trek in learning WebSharper, and I'm finding managing resources (e.g. css and javascript files) a bit confusing.
The documentation on this gives an example resource declaration
type MyResource() =
inherit Resources.BaseResource("http://my.cdn.net",
"file1.js", "file2.js", "file3.css")
and you can place a [<Require>] attribute on the assembly to make the resource appear on every page on the site:
[<assembly: Require(typeof<MyResource>)>]
do ()
The documentation goes on to say that the attribute can be placed on modules, types and (module level) let bindings and WebSharper will build a dependency graph to figure out if a given page (Action) needs the dependency or not. I've tried a few things (using a Twitter Bootstrap resource declaration), but the only way I've gotten to make this work is the assembly attribute.
Can someone give an example (or provide a link) of how to do properly use the [<Require>] attribute so that it gets added on one Action, but not another?
For reference, here's by Twitter Bootstrap resource declaration:
[<Require(typeof<JQuery.Resources.JQuery>)>]
[<Sealed>]
type BootstrapResource() =
inherit Resources.BaseResource("https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/",
"css/bootstrap.min.css", "js/bootstrap.min.js")
(I get that the [<Require]> attribute here here makes it a dependency of the Bootstrap resource, although I think WebSharper includes JQuery anyways, which would make it redundant.)

Having [<Require(typeof<JQuery.Resources.JQuery>)>] is not redundant as it tells WebSharper to always include JQuery before the currently defined resource.
Require attribute on assembly level in a referenced dll will not automatically add the resource to the page returned for a specific Action, only if the assembly referenced from one of the Web.Control types used for the page. It works the same for a smaller scope: [<Require(typeof<MyResource>)>] on a module, function, or type.

Related

Making a DuckDuckHack Instant Answer work on own site

I'm trying to see whether it's possible to use the code from DuckDuckHack Instant Answers outside of the context of the main DuckDuckGo website. After all, an Instant Answer is mostly a standalone component that consists of some HTML, CSS and Javascript and should be relatively self-contained and reusable.
Since the DuckDuckHack project is in maintenance mode, it seems to be a bit difficult to obtain information: the Slack and Forum mentioned on the Developer Guide have been disabled. I tried to find out whether other people tried to make Instant Answers work in other contexts, but I couldn't find anything. There are tutorials on writing Instant Answers, such as this using Perl and the DuckPAN tools. I tried to get an existing Instant Answer to work using those tools but I failed to get it to work properly (not sure whether they are still maintained).
Since those tools seem to be a bit heavyweight, I thought it could be possible to bypass them completely and try to just use the HTML, CSS and JS from the Instant Answer directly.
I've given the Calculator a first shot. I identified the relevant source code in the duckduckgo/zeroclickinfo-goodies repository's directory share/goodie/calculator. The HTML seems to be in the content.handlebars, and then there's calculator.css and calculator.js. I created a plain HTML file, put the HTML from content.handlebars in there and included references to the CSS file and the JS file. At first, the CSS wouldn't work because the CSS rules don't apply without adding some more wrapper divs that can be easily found when inspecting the DuckDuckGo site with the calculator Instant Answer visible. After adding those wrapper divs, the calculator UI appears more or less intact. However the buttons of the UI do not work at all yet.
Looking at the Javascript console I get this error:
Uncaught ReferenceError: DDH is not defined
at calculator.js:1
Apparently an object DDH is required by calculator.js, however I have no idea what that object should be and how I could create it.
Does anybody know how this DDH variable gets usually initialized or more generally how to make this work? Any reference to projects reusing the code from DuckDuckHack for their own sites would also be highly appreciated.
I think the details of how the DDH object is put together might not be exposed through the open-source portion of DDG.
I've also made some attempts to repurpose some of the built-in DDG IA functionality (e.g. get programmatic access to the many, useful instant answers unavailable through their API).
In the process, I cloned the goodie repo and poked around. The pattern seems to be that for most goodies the payload to be displayed is DDH.<goodie>.content. In my local copy of the goodie repo:
$ grep -rE 'DDH' ./lib/
./lib/DDG/Goodie/Conversions.pm: content => 'DDH.conversions.content'
./lib/DDG/Goodie/Conversions.pm: content => 'DDH.conversions.content'
./lib/DDG/Goodie/Game2048.pm: content => 'DDH.game2048.content'
./lib/DDG/Goodie/PublicDNS.pm: list_content => 'DDH.public_dns.content',
./lib/DDG/Goodie/JsBeautifier.pm: content => 'DDH.js_beautifier.content'
./lib/DDG/Goodie/SassToCss.pm: content => 'DDH.sass_to_css.content'
./lib/DDG/Goodie/Constants.pm: title_content => 'DDH.constants.title_content'
...
On the javascript side of things, you'd find the corresponding .js files in the share/goodie directory, as you did for the calculator goodie.
The <goodie>.js files seem to introduce DDH.<goodie> namespaces (typically right at the top of the file) and define DDH.<goodie>.build functions. This procedure is referenced in the goodie-display docs (see the section titled Setting Goodie Display Options on the Front end therein).
Nowhere in the docs, though, is it documented how the content property of DDH.<goodie> is put together. I cloned the docs repo and grepped for DDH, revealing nothing to that effect.
None of the .js files in the share/goodie folder of the goodie repo, though, seem to give a clue as to how DDH.<goodie>.content is assembled either. Indeed, some of those .js files do not even contain the string content. For a listing of all share*.js files that do contain the string content, I did (in the local copy of the goodie repo):
$ find ./share -name "*.js" |xargs grep -l 'content'
./share/goodie/countdown/countdown.js
./share/goodie/text_converter/text_converter.js
The other .js files do not contain that string at all..
Edit
On the other hand though, I see you did manage to get the calculator going with some modifications to the .js file (e.g. by inserting that missing DDH namespace).

Bundle is loaded in the wrong order

In my _Layout view I'm including the following scriptbundle:
bundles.Add(new ScriptBundle("~/bundles/js").Include(
"~/Scripts/jquery.min.js",
"~/Scripts/jquery-ui.min.js",
"~/Scripts/bootstrap.min.js",
"~/Scripts/jquery.flot.categories.js",
"~/Scripts/jquery.flot.js",
"~/Scripts/jquery.flot.min.js",
"~/Scripts/jquery.flot.orderBars.js",
"~/Scripts/jquery.flot.pie.js",
"~/Scripts/jquery.flot.resize.js",
"~/Scripts/graphtable.js",
"~/Scripts/fullcalendar.min.js",
"~/Scripts/chosen.jquery.min.js",
"~/Scripts/autoresize.jquery.min.js",
"~/Scripts/jquery.autotab.js",
"~/Scripts/jquery.jgrowl_minimized.js",
"~/Scripts/jquery.dataTables.min.js",
"~/Scripts/jquery.stepy.min.js",
"~/Scripts/jquery.validate.min.js",
"~/Scripts/jquery.cookie.js",
"~/Scripts/raphael.2.1.0.min.js",
"~/Scripts/justgage.1.0.1.min.js",
"~/Scripts/glisse.js",
"~/Scripts/styleswitcher.js",
"~/Scripts/moderniz.js",
"~/Scripts/jquery.sparkline.min.js",
"~/Scripts/slidernav-min.js",
"~/Scripts/jquery.fancybox.js",
"~/Scripts/main.js",
"~/Scripts/application.js",
"~/Scripts/excanvas.min.js",
"~/Scripts/float.settings.infobox.js"
));
The thing is; when I load the page and look at the network traffic I see that jquery-ui.min.js is loaded first (before jquery.min.js)!
Here is the order in which they load in:
I thought the scripts would load in the order I specified them in. Am I wrong? And if so, how should I load them in the proper order?
EDIT:
I tried adding a custom sort as described here:
http://stevescodingblog.co.uk/changing-the-ordering-for-single-bundles-in-asp-net-4/
However I get the following error when building the project:
Error 1 'Namespace.AsIsBundleOrderer' does not implement interface member 'System.Web.Optimization.IBundleOrderer.OrderFiles(System.Web.Optimization.BundleContext, System.Collections.Generic.IEnumerable)'
Some things that can cause this problem:
Buggy version of ASP.NET Web Optimization Framework. Update to 1.1.3 or later.
Explicit names are always processed before symbolic names.
Explicitly named dependent of symbolically named libraries
Knockout-jqueryui.js doesn't have a version in its name. It depends on jQuery and jQuery UI. Both of these have versions in their names. If you use symbolic names like jQuery-{version} to refer to these libraries, because knockout-jqueryui is named explicitly, it will be loaded in the first pass - before its dependencies.
Either name the versions explicitly or fudge a version number into the dependent library name and make them all symbolic.

How to use javascript in Silverstripe CMS?

I'm using SilverStripe 3.0 CMS, and I need to include a Google Map into the CMS.
I'm following this steps, and besides it's a little bit old, the official documentation uses the same methods in the current version of SilverStripe (At least it seems to be the current version documentation).
The issue is in this part of the code:
Behaviour.register({
"#Form_EditForm" : {
initialize : function() {
this.observeMethod("PageLoaded", this.adminPageHandler);
this.adminPageHandler();
},
adminPageHandler : function() {
initialize();
}
}
});
First of all, Behaviour was not defined. I needed to include manually the behaviour.js file that comes within the framework. But now, I get a Type Error:
this.observeMethod is not a function
Can someone give me a hint of what can I do in order to call a javascript function when a page editor is opened in the SilverStripe CMS?
the 'Behaviour.register' call you mention is definitly deprecated and no longer available in the core code, so the docs need an update here.
unfortunately, i couldn't find a documented way to replace this behaviour, but for now the following should work for you, based on the approach in the forum post you mentioned first hand:
find the 'initGoogleMaps.js' script added here:
function getCMSFields() {
Requirements::javascript('mysite/javascript/initGoogleMaps.js');
...
inside this script, remove the Behaviour.register... block, and move the initialize function outside document.ready (or simply remove the document.ready part), so initialize is globally available (you might consider renaming it).
then, add the following inside getCMSFields:
$fields->addFieldToTab('Root.Content', new LiteralField('js', '<script>initialize();</script>'));
this will ensure the initialize function is called every time a page's 'edit view' is rendered inside the cms.
hth
As mentioned by ben,
LeftAndMain::require_javascript('mysite/javascript/initGoogleMaps.js')
is more reliable than 'include-it when needed'. Why?
Because Silverstripe uses Ajax, it is better to load any javascript or css on the first load, so that they are ready when you go to different model admin areas within the CMS in ajax-powered environment. Not loading at the start causes inconsistency and your js, css files will not be loaded when you don't hard-load that admin area.
From documentation: http://doc.silverstripe.org/framework/en/reference/requirements and http://api.silverstripe.org/3.0/class-LeftAndMain.html
The whole "include it when you need it" thing shows some weaknesses in
areas such as the CMS, where Ajax is used to load in large pieces of
the application, which potentially require more CSS and JavaScript to
be included. At this stage, the only workaround is to ensure that
everything you might need is included on the first page-load.
One idea is to mention the CSS and JavaScript which should be included
in the header of the Ajax response, so that the client can load up
those scripts and stylesheets upon completion of the Ajax request.
This could be coded quite cleanly, but for best results we'd want to
extend prototype.js with our own changes to their Ajax system, so that
every script had consistent support for this.
By the way, the ideal place for this line is _config.php in your custom module or in mysite, depending on your needs.
LeftAndMain::require_javascript('mysite/javascript/initGoogleMaps.js')
would work much better

How to load page specific JS files with RequireJS?

I have a multiple page website using RequireJS, which loads a boot strap file (boot.js), which then requires app.js.
app.js handles all the logic, and all other module initialization happens through app.initModule() (which is just a require() call wrapper)
I also have a app.loadPageJS() to load page specific JS files (based on window.location.pathname, for example, www.domain.com/path/to/file.html would auto-load /_assets/js/pages/path/to/file.js)
This feature can be turned on/off, and overridden by adding a class of "no-auto-load" or "auto-load" to the body, respectively.
Now, my approach isn't robust enough. For one, url rewriting would break the mechanism, and for two, if loadPageJS is turned off, unless I have access to the body tag, I can't include a page specific JS file (in the case of sites using templating systems, adding a class to the body tag isn't always an option).
What are other ways to include page specific code? I'd rather avoid the following:
adding page specific code to a global.js file and doing if checks and only running certain code sets
using a pageName variable (which would essentially be similar to the above)
Thanks in advance.
If you have different modules on the page sectioned by unique ID's (a newsletter module wrapped within a div with an ID of 'newsletter', etc), you could test for existence of the module element in the DOM and conditionally load in the JS file necessary to run that module. So rather than being page-specific, it is module specific.

Creating a javascript widget for other sites

I am looking to create a javascript "widget" that can be hosted on other sites. For example. I want to host the javascript code on my site:
http://scripts.mysite.com/mywidget.js
(Think of it like Google Analytics).
Basically I want to distribute data via this javascript. But what I am unsure of is:
Are the rules different for development when creating a javascript for another site (cross site)
Are there any websites that explain these differences?
I would try to:
Make it configurable
Load external stylesheets?
Where do I find resources I need? (images, stylesheets)
What layout/size should I have?
By doing this you let the user decide if he wants your widget to automatically load the stylesheet, or if he wants to host it himself. If he does, he can also update the styles to better fit the page the widget resides on.
Provide a wizard for generating a snippet of code to include on the page
Ensures that even moderately technical users can utilize your widget
Make it light-weight
Serve everything minified and compressed
Serve with cache-headers, e-tags, last-modified and all other useful headers you can think of. This will both reduce the load on your servers as well as make your widget more responsive.
Try to avoid dependencies on libraries, or check if they are loaded on the page where the widget is used before loading them
Be wary of conflicts
Prototype uses $, and so does jQuery. If your widget depends on Prototype, and the page it is hosted on uses jQuery without noConflict-mode, problems WILL arise
Do not clobber the global namespace!
If you don't want anyone to interact with your widget, put it in a self-executing function in a closure and don't create any global variables at all
If you want users to be able to interact with your widget, say for adding event listeners and such, claim a single global variable, let's say ExampleComWidget as an object literal and put your methods there. User's could then interact like: ExampleComWidget.addListener('update', callback);
Use clever markup
Be sure to use scoping on your classes and ids, to avoid conflicts as much as possible
I.e. if your company's name is example.com, you could use classes like: com-ex-widget-newsItem
Validate your markup! You do not want to break a user's site
Semantic markup is fine, but you might want to avoid <h1>-tags, since they have especially high ranking in SEO. You could probably get by with using <h4> and less. This bullet might be a bit off. When in doubt, it's much better to use semantic markup than not.
Fetch data from your site by inserting script elements
This is a fool-proof way to ensure that you get around the same-origin restrictions.
Attach an onload-listener to the script element to know when the data is ready, or use jsonp
Your script should not interfere with the rest of the page.
Keep the number of globals to a
minimum (one namespace object
should be enough)
Don't add properties to the built-in
objects for no reason
Don't expect to be the only script
that listen for events such as window.onload
If you're using for..in loops keep in mind
that other scripts may have added
stuff to Array.prototype
Take style-sheets into consideration. The default style of HTML elements may have been changed.
Don't update your script without reason as you risk breaking a lot of sites.
What PatrikAkerstrand said, is totally, 100% right.
What I want to add here, is a framework in vanilla JS that can save you a lot of time and effort to implement it, as everything is thought of, polished, and tested. All is left is to define your own widgets, and use them.
Here's an example of what it looks like.
A widget code
// inside a js file of a widget class
(function () {
var Module_Path = ["WidgetsLib", "a1", "Button"];
var curr = this;
Module_Path.forEach(function(i){if (curr[i] == null) {addChildToTree(curr, i, {})} curr = curr[i]});
specialize_with(curr, {
CSS_Literal: `
.{{WIDGET_CLASS_ID}}_Something {
color: hsl(0, 0%, 20%);
}
`,
HTML_Literal: `
<div onclick="{{WIDGET_INSTANCE}}.onclick(event)"
class="{{WIDGET_CLASS_ID}}_Something"
>
SOME SUPER COOL WIDGET
</div>
`,
new: typical_widget_new,
});
})();
An HTML:
<div id="w1" style="background-color: hsl(200, 50%, 50%);"></div>
<script src="WidgetsLib/a1/Button/js"></script>
A user JavaScript code:
var b1 = WidgetsLib.a1.Button.new("w1", {
onclick: function(ev) {
ev.target.style.color = "#ffff00";
console.log("====== HERE");
}
});
Please download, and open the Widget_v2.md.html in a browser, it's https://github.com/latitov/JS_HTML_Widgets .
What you'll get:
very interesting article about this;
snippets of code and examples;
ready to use... framework in vanilla JS, to create widgets of your own;
And enjoy creating re-usable widgets of your own, of arbitrary complexity, easily!

Categories