Dojo 1.10+
I want to conditionally load custom module according to this post
Dojo FAQ: How can I conditionally load AMD modules?
require([
'dojo/has'
], function (has) {
var ui;
var moduleId = 'myApp/ui/';
// Assume 'has' tests for mobile and tablet
// have been defined
if (has('mobile')) {
moduleId += 'Mobile';
}
else if (has('tablet')) {
moduleId += 'Tablet';
}
else {
moduleId += 'Desktop';
}
require([moduleId], function (UiModule) {
ui = new UiModule();
ui.placeAt(document.body);
ui.startup();
});
});
However it seems dojo/has only detects certain dojo features. If that is the case is there an alternative method to detect if a custom module exists before attempting to require and then instantiate it if its a widget?
Yes, dojo/has only detects certain dojo features. But you can add your own, i was trying something like you are trying here, maybe not the best, but this work for me.
At boot time of your app, you can add you own features to dojo, i.e.
var deviceWidth = has('device-width'), hasTouch = has('touch');
has.add('mobile', (hasTouch && deviceWidth <=736));
has.add('tablet', (hasTouch && (deviceWidth > 736 && deviceWidth <=1024)));
and so on, so, later in another widget, you can require dojo/has and use the recently added features. Or event better, you can do this
require(['dojo/has!mobile?myApp/ui/Mobile:myApp/ui/Tablet'], function (UiModule) {
ui = new UiModule();
ui.placeAt(document.body);
ui.startup();
});
I've never tried nested dojo/has validations and i can not tell you if it works, but i've tried like in the example i'm giving you and it works.
Think that you can have in your project your own has lib extending the dojo version and adding all the features you want. For example:
//myApp/has.js
define(["dojo/has"], function(has){
has.add('myApp', true);
return has;
});.
Then later in your app you can include myApp/has and us it just like if it were the dojo version.
Related
We have a project with jstree 1.0-rc3. To extend basic funtionality we wrote custom extention using JQuery.jstree.plugin() function. Other words, we created custom plugin and used it as a usual plugin. Recently we decided to update jstree version to latest (3.3.8) and I met trouble because this function (JQuery.jstree.plugin) do not exist anymore and I can not find any analogue. Because of size of our custom plugin (hundreds of lines) and big count of place where we use it we do not want to move functionality to some another place from custom plugin cause it will take a really lot of time. Does anyone know how is it possible to create custom plugin in latest jstree ( 3.3.8 now)? Many thanks.
The functionality to add custom plugin is still there in the current jsTree version (3.3.8). As an example, it is already explained
// conditional select
(function ($, undefined) {
"use strict";
$.jstree.defaults.conditionalselect = function () { return true; };
$.jstree.plugins.conditionalselect = function (options, parent) {
this.activate_node = function (obj, e) {
if(this.settings.conditionalselect.call(this, this.get_node(obj))) {
parent.activate_node.call(this, obj, e);
}
};
};
})(jQuery);
$("#tree").jstree({
"conditionalselect" : function (node) {
return node.text === "Root node" ? false : true;
},
"plugins" : ["conditionalselect"]
});
Please the approach explained in https://github.com/vakata/jstree#more-plugins how to included your own plugin or any third party plugins.You must include its source on the page and list its name in the "plugins" config array.
You can have a look at jstree/src/misc.js for finding many such custom plugins already written.
I am trying to update a Rails 3 app to Rails 6 and I have problems with the now default webpacker since my Javascript functions are not accessible.
I get: ReferenceError: Can't find variable: functionName for all js function triggers.
What I did is:
create an app_directory in /app/javascript
copied my development javascript file into the app_directory and renamed it to index.js
added console.log('Hello World from Webpacker'); to index.js
added import "app_directory"; to /app/javascript/packs/application.js
added to /config/initializers/content_security_policy.rb:
Rails.application.config.content_security_policy do |policy|
policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
end
I get 'Hello World from Webpacker' logged to console, but when trying to access a simple JS function through <div id="x" onclick="functionX()"></div> in the browser I get the reference error.
I understand that the asset pipeline has been substituted by webpacker, which should be great for including modules, but how should I include simple JS functions? What am I missing?
Thanks in advance?
For instructions on moving from the old asset pipeline to the new webpacker way of doing things, you can see here:
https://www.calleerlandsson.com/replacing-sprockets-with-webpacker-for-javascript-in-rails-5-2/
This is a howto for moving from the asset pipeline to webpacker in Rails 5.2, and it gives you an idea of how things are different in Rails 6 now that webpacker is the default for javascript. In particular:
Now it’s time to move all of your application JavaScript code from
app/assets/javascripts/ to app/javascript/.
To include them in the JavaScript pack, make sure to require them in
app/javascript/pack/application.js:
require('your_js_file')
So, create a file in app/javascript/hello.js like this:
console.log("Hello from hello.js");
Then, in app/javascript/packs/application.js, add this line:
require("hello")
(note that the extension isn't needed)
Now, you can load up a page with the browser console open and see the "Hello!" message in the console. Just add whatever you need in the app/javascript directory, or better yet create subdirectories to keep your code organized.
More information:
This question is cursed. The formerly accepted answer is not just wrong but grotesquely wrong, and the most upvoted answer is still missing the mark by a country mile.
anode84 above is still trying to do things the old way, and webpacker will get in your way if you try that. You have to completely change the way you do javascript and think about javascript when you move to webpacker. There is no "scoping issue". When you put code in a web pack it's self-contained and you use import/export to share code between files. Nothing is global by default.
I get why this is frustrating. You're probably like me, and accustomed to declaring a function in a javascript file and then calling it in your HTML file. Or just throwing some javascript at the end of your HTML file. I have been doing web programming since 1994 (not a typo), so I've seen everything evolve multiple times. Javascript has evolved. You have to learn the new way of doing things.
If you want to add an action to a form or whatever, you can create a file in app/javascript that does what you want. To get data to it, you can use data attributes, hidden fields, etc. If the field doesn't exist, then the code doesn't run.
Here's an example that you might find useful. I use this for showing a popup if a form has a Google reCAPTCHA and the user hasn't checked the box at the time of form submission:
// For any form, on submit find out if there's a recaptcha
// field on the form, and if so, make sure the recaptcha
// was completed before submission.
document.addEventListener("turbolinks:load", function() {
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function(event) {
const response_field = document.getElementById('g-recaptcha-response');
// This ensures that the response field is part of the form
if (response_field && form.compareDocumentPosition(response_field) & 16) {
if (response_field.value == '') {
alert("Please verify that you are not a robot.");
event.preventDefault();
event.stopPropagation();
return false;
}
}
});
});
});
Note that this is self-contained. It does not rely on any other modules and nothing else relies on it. You simply require it in your pack(s) and it will watch all form submissions.
Here's one more example of loading a google map with a geojson overlay when the page is loaded:
document.addEventListener("turbolinks:load", function() {
document.querySelectorAll('.shuttle-route-version-map').forEach(function(map_div) {
let shuttle_route_version_id = map_div.dataset.shuttleRouteVersionId;
let geojson_field = document.querySelector(`input[type=hidden][name="geojson[${shuttle_route_version_id}]"]`);
var map = null;
let center = {lat: 36.1638726, lng: -86.7742864};
map = new google.maps.Map(map_div, {
zoom: 15.18,
center: center
});
map.data.addGeoJson(JSON.parse(geojson_field.value));
var bounds = new google.maps.LatLngBounds();
map.data.forEach(function(data_feature) {
let geom = data_feature.getGeometry();
geom.forEachLatLng(function(latlng) {
bounds.extend(latlng);
});
});
map.setCenter(bounds.getCenter());
map.fitBounds(bounds);
});
});
When the page loads, I look for divs with the class "shuttle-route-version-map". For each one that I find, the data attribute "shuttleRouteVersionId" (data-shuttle-route-version-id) contains the ID of the route. I have stored the geojson in a hidden field that can be easily queried given that ID, and I then initialize the map, add the geojson, and then set the map center and bounds based on that data. Again, it's self-contained except for the Google Maps functionality.
You can also learn how to use import/export to share code, and that's really powerful.
So, one more that shows how to use import/export. Here's a simple piece of code that sets up a "watcher" to watch your location:
var driver_position_watch_id = null;
export const watch_position = function(logging_callback) {
var last_timestamp = null;
function success(pos) {
if (pos.timestamp != last_timestamp) {
logging_callback(pos);
}
last_timestamp = pos.timestamp;
}
function error(err) {
console.log('Error: ' + err.code + ': ' + err.message);
if (err.code == 3) {
// timeout, let's try again in a second
setTimeout(start_watching, 1000);
}
}
let options = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 14500
};
function start_watching() {
if (driver_position_watch_id) stop_watching_position();
driver_position_watch_id = navigator.geolocation.watchPosition(success, error, options);
console.log("Start watching location updates: " + driver_position_watch_id);
}
start_watching();
}
export const stop_watching_position = function() {
if (driver_position_watch_id) {
console.log("Stopped watching location updates: " + driver_position_watch_id);
navigator.geolocation.clearWatch(driver_position_watch_id);
driver_position_watch_id = null;
}
}
That exports two functions: "watch_position" and "stop_watching_position". To use it, you import those functions in another file.
import { watch_position, stop_watching_position } from 'watch_location';
document.addEventListener("turbolinks:load", function() {
let lat_input = document.getElementById('driver_location_check_latitude');
let long_input = document.getElementById('driver_location_check_longitude');
if (lat_input && long_input) {
watch_position(function(pos) {
lat_input.value = pos.coords.latitude;
long_input.value = pos.coords.longitude;
});
}
});
When the page loads, we look for fields called "driver_location_check_latitude" and "driver_location_check_longitude". If they exist, we set up a watcher with a callback, and the callback fills in those fields with the latitude and longitude when they change. This is how to share code between modules.
So, again, this is a very different way of doing things. Your code is cleaner and more predictable when modularized and organized properly.
This is the future, so fighting it (and setting "window.function_name" is fighting it) will get you nowhere.
Looking at how webpacker "packs" js files and functions:
/***/ "./app/javascript/dashboard/project.js":
/*! no static exports found */
/***/ (function(module, exports) {
function myFunction() {...}
So webpacker stores these functions within another function, making them inaccessible. Not sure why that is, or how to work around it properly.
There IS a workaround, though. You can:
1) change the function signatures from:
function myFunction() { ... }
to:
window.myFunction = function() { ... }
2) keep the function signatures as is, but you would still need to add a reference to them as shown here:
window.myFunction = myFunction
This will make your functions globally accessible from the "window" object.
Replace the code in your custom java Script file
from
function function_name() {// body //}
to
window.function_name = function() {// body //}
From the official rails app guide:
Where to Stick Your JavaScript
Whether you use the Rails asset
pipeline or add a tag directly to a view, you have to make a
choice about where to put any local JavaScript file.
We have a choice of three locations for a local JavaScript file:
The app/assets/javascripts folder,the lib/assets/javascripts folder and the vendor/assets/javascripts folder
Here are guidelines for selecting
a location for your scripts:
Use app/assets/javascripts for JavaScript you create for your
application.
Use lib/assets/javascripts for scripts that are shared by many
applications (but use a gem if you can).
Use vendor/assets/javascripts for copies of jQuery plugins, etc., from
other developers. In the simplest case, when all your JavaScript files
are in the app/assets/javascripts folder, there’s nothing more you
need to do.
Add JavaScript files anywhere else and you will need to understand how
to modify a manifest file.
More reading:
http://railsapps.github.io/rails-javascript-include-external.html
We use extjs v4.2 and sencha touch v2.3
I'm currently working on a project that involves loading the controller dynamically. This works perfectly well in extjs for me, but I can't get the same functionality in sencha touch.
When i want to load a controller in extjs I use the following syntax
var controllerLookup = 'here.app.controller.' + controllerName;
AppName.app.getController(controllerLookup);
This then goes back to the server, downloads the controller and calls its init method.
I would like the exact same functionality in sencha touch. From reading the docs the syntax looks like it should work similarly, but i can't seem to get it to work. when i run the code in the console i just get an undefined. No call to the server like I do in extjs looking for the file. Can anyone see where I'm going wrong or if it's even possible to do in sencha touch.
Thanks
With the help of a post on the sencha forums (https://www.sencha.com/forum/showthread.php?198019-How-to-load-controllers-dynamically) I was able to get it to work. The controller now loads in dynamically and calls the init method
AppName
The name you used in Ext.Application when defining your application in app.js or equivalent.
Classpath
The full namespace to you controller file
function addControllerDynamicallyForMobile(classPath, config) {
var app = AppName.app.application,
config = config || {};
Ext.Loader.setConfig({ enabled: true });
Ext.require(classPath, function() {
var controllers = app.getControllerInstances();
if (!controllers[classPath]) {
var controller = Ext.create(classPath, Ext.apply({
application : app
}, config.options || {}));
controllers[classPath] = controller;
app.controllers.push(classPath);
controller.init();
if (config.callback) { config.callback.call((config.scope || this), config); }
}
});
};
calling this function
var controllerLookup = 'here.app.controller.' + controllerName;;
addControllerDynamicallyForMobile(controllerLookup)
I'm running a project where people can find doctors on the map and And book Online , ...
Previously I decided to use Angularjs and change the whole project, so I had to forget about some jQuery plugins which I've used before.
**Problem ** :
I'm using a jQuery plugin that works awesome with Google map API (Landcarte) , and I haven't find anything else to compare with this plugin in AngularJS.
So I couldn't do anything but to use both jquery and angular and this plugin in my site , But I dont know , I feel that its wrong to use both jquery and angular because I think that makes my firstload heavy.
**Questions : **
1- Is this possible to convert this plugin into a normal Javascript so I can omit the Jquery in my site ?
2- If not , What can I do ?
3- Can I use RequireJS to load jquery and this plugin later in my site ? (I dont know how to :()
I don't know about the Landcarte plugin so I can't help you with question 1.
If you want to initialize a jquery plugin but it's not working, a common cause of the problem is that the DOM is not ready yet.
To solve this, there are three options:
Method 1 Initialize the plugin inside of the link property of your directive. Within the link function, the children of the directive element have already been compiled and linked. If your plugin relies only on the children of the element being DOM ready, then this option is suitable.
app.directive('myDirective', function(){
return {
link: function(scope, element,attr){
element.plugin();
}
}
});
Method 2 Using $evalAsyc which runs after the compile and link phase but before the Render phase. Use this method if your plugin relies on the entire page being DOM ready, but it is not important that expressions have been rendered.
app.directive('myDirective', function(){
return {
link: function(scope, element,attr){
scope.$evalAsync(function(scope){
element.plugin();
});
}
}
});
Method 3 Using $timeout which runs after the render phase. Use this method if your plugin relies on the entire page being DOM ready, and all the expressions have been rendered.
app.directive('myDirective', function($timeout){
return {
link: function(scope, element,attr){
$timeout(function(){
element.plugin();
});
}
}
});
Depending on the plugin, one of these options should work for you. Prefer one that meets the need of the plugin minimally - meaning prefer option 1, over option 2, over option 3, but ultimately go with the one that works.
To turn min.js file into normal.js you can use this
but it just set tabulations and spaces and make script readable.
For example this script:
var a={log:function(){try{var e=Array.prototype.slice.call(arguments);if(typeof e[0]==="string"){if(/[\{\}]/.test(e[0])&&e.length>1){var t=e.length>2?e.slice(1):e[1];return console.log(this.text(e[0],t))}}return console.log.apply(console,e)}catch(n){}}}
will be:
var a = {
log: function () {
try {
var e = Array.prototype.slice.call(arguments);
if (typeof e[0] === "string") {
if (/[\{\}]/.test(e[0]) && e.length > 1) {
var t = e.length > 2 ? e.slice(1) : e[1];
return console.log(this.text(e[0], t))
}
}
return console.log.apply(console, e)
} catch (n) {}
}
}
Landcarte can be used in a pure JS code without jQuery as well. A map can be initialized by an explicit call of the at.geo.Map class constructor:
var container = document.getElementById("map");
var map = new at.geo.Map(container);
This class is mentioned in the reference.
I'm working on an app that uses Backbone and RequireJS (using the Backbone boilerplate).
What I'd like to do is detect if the user is on a mobile device (currently using Modernizr to check for touch), and if so then load jQuery Mobile's css and js, and have it applied to all modules and their templates.
I'm not sure of the best way to do this with RequireJS.
Cheers
Edit: After re-reading your question, I'm not sure this is what you're asking for. Sorry for the noise.
I'm currently in a similar situation. You can set properties on Backbone's View/Router/Model/Etc prototypes and they'll filter down the chain. So, for example:
//Assuming we're in 'mobile' mode, you'd do this in whatever code gets loaded.
Backbone.View.prototype.useMobileView = true;
Then you can do whatever is necessary if this.useMobileView == true. For example:
ResponsiveView = Backbone.View.extend({
getTemplate: function () {
if(this.useMobileView) {
return this.mobileTemplate;
} else {
return this.template;
}
}
});
SomeView = ResponsiveView.extend({
render: function () {
var template = this.getTemplate();
//do stuff
}
});