Event handling with jQuery, AMD and require.js - javascript

I am very new to AMD and require.js and have been struggling with a little problem for almost over a day. I've tried different ways but not sure what is the correct way/right approach. I would appreciate some feedback from all the JavaScript gurus.
I am trying to implement an event handler for a text box that will listen for any input/changes. In the event handler, I would like to update a marker currently being shown on a map. So, I defined two modules - open for openlayers and one containing my custom code for displaying the map, updating markers etc.
The custom module looks like below:
define('mymodule', [ 'open-layers', 'jquery', 'openstreetmaps','t5/core/console' ], function(
openLayers, $,openStreetMaps ,console) {
var init = function() {
}
var listenForChange = function(clientId) {
clientId = clientId;
var textBox = $(document.getElementById(clientId));
console.debug(textBox);
$('#addressLineTwo').on('change paste keypress input', function() {
console.debug(textBox);
console.debug('OnChange');
console.debug($(this));
console.debug($(textBox).val());
openstreetmaps.clearMarkersAndShowAddress();
});
}
return {
init: init,
listenForChange: listenForChange
};
});
The problem is, when the event handler gets called as a result of input in the text field, the openstreetmaps variable in function defined as handler for "on" method is undefined.
They only way I could get around this was to change that line to something like this:
require(['openstreetmaps'], function(openstreetmaps) { openstreetmaps.clearMarkersAndShowAddress(newAddress);});
Also, if I use such a construct how do I manage variables and passing data between all those closures? It seems like that the inner closure cannot access variables from the outer closures or functions.
I would really appreciate your help and feedback.

The variables from your other modules are accessible from within the event handler, you just need to change the define function parameter of openStreetMaps to openstreetmaps. That's why the extra require call works; you just need to match up the variable naming with your required module.
For managing variables, just expose everything public by returning them from the module, and those public variables can access whatever is defined in the module which creates a closure. And each module only gets registered once so you can retain whatever state you want in each module.

Related

Overriding jQuery function loses 'this' scope

I am trying to implement the following answer from another question:
https://stackoverflow.com/a/26469105/2402594
Basically I need to add an extra check to a jQuery function. The following code is in jQuery library:
But I can't modify the original jQuery, so I am creating a patch in a separate file. What I am doing is overriding the find function and add functionality as follows:
(function() {
var originalFind = jQuery.fn.find;
jQuery.fn.find = function () {
try {
document === document;
}
catch (err) {
document = window.document;
}
return originalFind.apply(this, arguments);
};
})();
The function is overridden correctly, however, when the code calls 'find', my 'try' doesn't throw any exception when it should because the scope is different than the one inside the Sizzle function, so the original issue is still there.
I have also tried duplicating all of the Sizzle code, adding my modification and assigning it to jQuery.fn.find as done above, however the scope issue is still there and some crashes happen.
I need 'document' to be set before it reaches the following check or it crashes due to permission denied:
How could I share the scope so that the try/catch can be done correctly? Is it even possible? Any other ideas?
Thank you
As we all known, JavaScript has function scope: Each function creates a new scope. Scope determines the accessibility (visibility) of these variables. Variables defined inside a function are not accessible (visible) from outside the function.
So, if the document is defined in the JQuery library function, I think you can't access it. You could try to define a global variable to store the document, then, you can access it in the JQuery library function and the override function.
More details about Javascript Scope, check these links: link1 and link2

Is it possible to avoid a global here? And is it worth it?

I have a web application that displays a google map. When a user clicks somewhere on a polygon on the map, a kendo grid on the same page is populate via an ajax call that a kendo datasource repesents.
When data are fetched by the kendo grid, I also use the same data to populate a tooltip on the google map. This tooltip needs to be displayed at the same place where user clicked.
In order to pass the coordinates of this click I'm using a global variable. I would like to know if it's possible to avoid it, and also if it's worth the trouble.
Here are excerpts from my code.
Define the global:
var clickLatLng;
Subscribe for the click event on the map:
google.maps.event.addListener(polygon, 'click', function(event) {
//Keep track of coordinates in a global
clickLatLng = event.latLng;
// initiate the ajax call to get the data
dataSource.read(this.objInfo.id);
// update grid view
var grid = $("#grid").data("kendoGrid");
grid.setDataSource(dataSource);
grid.refresh();
});
Define the datasource:
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "/getdata",
type: "GET",
dataType: "json",
contentType: "application/json"
}
},
schema: {
data: function (response) {
var events = [];
var eventList = response.EventList;
if (!eventList) {
return events;
}
// This fills events from eventList
populateEvents(events, eventList);
// see below
setToolTipValues(events);
return events;
},
...
}
});
Display the tooltip:
var setToolTipValues = function (events) {
var infoWindow = new google.maps.InfoWindow();
infoWindow.setContent(getTooltipConent(events));
// here we use the global saved earlier to display the tooltip at the correct coordinates
infoWindow.setPosition({ lat: clickLatLng.lat(), lng: clickLatLng.lng() });
// map is also global I'm guessing there is no work around that
infoWindow.open(map);
}
Update: to add some more information, the code above is located in pageHelper.js
pageHelper.js looks like this:
var pageHelper = function($, google, model) {
var clickLatLng;
...
}
And this is how it's called from the page:
<script type="text/javascript">
var model = ...; //the ... bit comes from the server
pageHelper($, google, model);
</script>
Update 2
Sequence of events:
User clicks a poligon
google.maps.event.addListener(polygon bit of code fires
it calls dataSource.read which initiate the ajax call
when the ajax call is finished data: function (response) { is fired
It calls setToolTipValues to display the tooltip
If you want to store a value for later consumption by other events, you have several choices which are not globals:
You can create a common closure (usually an IIFE) containing all code that needs to access the value and store the value in a local variable in the closure.
You can store the value as a property of some existing object that all code that needs to use that value can access. This could be a DOM object or some other object you have in your code. jQuery's .data() provides a simple way to associate a value with a DOM object without using a global.
You can create a single global namespace object and put your value on that single namespace object as a property. While this uses a global, it allows you to have many global properties while only consuming one single name in the global namespace.
If the value is being used as all part of one main event trigger, then you can usually find a way to pass the variable along so other pieces of the event handling can use the original value rather than storing it in a global. I can't tell the exact sequence of events in your code to know whether this is possible or not.
In general, it is a good idea to avoid globals for a variety of reasons. It isn't a hard rule, but it is a guideline that is considered generally good practice. If you find a need for multiple globals, you can also use a single global namespace object (this is what frameworks like jQuery do) so you're only creating one new globally accessible symbol.
Update based on some clarification from the OP:
It sounds like you already aren't storing it in a global at all, but rather it's in a local variable in a closure. That's probably all you need to do as there really isn't a whole lot of advantage to storing it somewhere else.
Because you want to use the value in a method that is called indirectly by some other part of the system, you can't directly pass the value along as just function arguments, so the typical Javascript solution to this is to store it in a parent closure where you can then access it from the callback function when that is called later. That's the usual Javascript design pattern for this type of problem.
So, if this understanding is correct:
User clicks polygon.
You read data from the data source.
The schema.data method in the data source is called by the kendo framework as part of reading from that data source.
You need to use the click location in that schema.data callback.
Then, the best you can do here is to put the value in a parent closure where that scheme.data callback can get to the original click location.
I might personally make one change to the way your code is structured. I'd probably pass the position into setToolTipValues() as an argument and fetch the closure variable to pass to it inside of schema.data(). This makes setToolTipValues() independent of how the location was obtained and keeps the dependency on this closure variable in the one place it can't be avoided.

extend a javascript option to add functionality

I need to call "MyOtherFunction" when "MyFunction"(which creates an element) completes, without MyFunction knowing what MyOtherFunction is.
The reason I need this is for extension of a jquery powered fileupload User Control that is used in several places with different functionality. A specific page shows a header and file count for it, and when the upload completes, I need to modify the file count according to how many files are displayed(by created elements) I thought :
$(UserControl).on(MyFunction, UploadElem, MyOtherFunction);
but this route is not accomplishing anything. The most I can alter the User Control is add in a function call, but without effecting the original user control functionality.
I'm not sure if because MyFunction isn't an event and doesn't bubble up or if it just isn't possible to use a defined function as a parameter of .on() is the reason I cannot get this code to work. Any suggestions?
Easiest way I can think of, is duck punching respectively hooking that method:
var _oldMyFunction = MyFunction;
MyFunction = function() {
_oldMyFunction.apply( this, arguments );
MyOtherFunction();
};
I managed to solve my own issue, but the context is important for the answer:
// Using a Global JavaScript object I created:
GlobalNameSpace.ExtensionFunction = function(oParam1, oParam2, oParam3)
{
/// <summary>All parameters are optional</summary>
return; // For instances when it is not being overwritten, simply return
}
//In the Code for the user control:
GlobalNameSpace.UploadControl.UploadComplete(oSender, oArgs)
{
///<summary>Handles the Upload process</summary>
// process the upload
GlobalNameSpace.ExtensionFunction(oSender, oArgs);
}
//and finally in the code to extend the functionality
GlobalNameSpace.Page.Init
{
///<summary>Initializes the page</summary>
// redefine the extension function
GlobalNameSpace.ExtensionFunction = function(oSender, oArgs)
{
GlobalNameSpace.Page.Function(oSender, oArgs);
}
}
This allows me to extend anything I need it to without polluting my objects, and having something generic already existing to call on to make my changes. This solution solves my problem of needing a onCreate function for the elements I create to represent my uploaded items to trigger the header displaying the number of files. Very useful

Backbone.js (with Require.js) variable/scope access issue

I'm trying to figure out how best to resolve an architectural issue with Backbone.js/Require.js
I have a test project here: https://github.com/Integralist/Backbone-Playground
The problem I'm having is that I'm creating a View in my main script file and then in another View script file I'm trying to access the other View but I don't know how to do it other than setting a global variable/property?
https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L73 is where I'm setting the global and https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/Views/Contacts.js#L34-35 is where I'm accessing it.
I can't seem to wrap my head around how else to access it.
I know that this is just one global being set and if I had to keep it like that then I could also limit any damage by namespacing the global like so: window.myapp.contact_view = new ContactView(...) but this feels like an ugly workaround for this type of scope issue.
Any advice greatly appreciated.
UPDATE: Addy Osmani from Google has since tweeted me to suggest that namespacing my global is the best thing I can do in this instance, but I'll leave this question open for a while to see if there are any other suggestions that crop up.
The guru has spoken :) (Addy Osmani)
Here's what you can/should do IMHO - wrap your code in a self-executing function block:
File1.js:
(function(myNameSpace){
// do something
myNameSpace.sharedValue = something();
})(window.myNameSpace = window.myNameSpace || {});
Repeat the exact same code (structure) in File2.js
The last line makes sure that whichever file is loaded first the object is created and the same object is then used across files. Passing that same argument to the function allows you to access myNameSpace object in the function itself.
Other files can extend/augment/use the object as they deem fit. Basically there is no way to share variables in javascript (across files) other than exposing globals but can be done in a nice way :)
Try this for ContactsView.js:
define([ 'backbone' ],
function(B) {
return B.View.extend({
display_selected: function(event) {
this.options.contactView.render(model);
}
})
})
In your code,
new ContactsView({
contactView: new ContactView( ... )
})
Anyway, you should probably create your injected view from ContactsView and just pass the required information as extra option values.
I've decided my best option is to use the global namespace as suggested by Addy Osmani, BUT I think if I needed an alternative option then I would just set an instance specific property like so:
contacts_view.contact = new ContactView({
el: $('#view-contact')
});
I can then access the View's render() method from within contacts_view like so...
var ContactsView = Backbone.View.extend({
events: {
'change select': 'display_selected'
},
display_selected: function (event) {
this.contact.render(model);
}
});
...this feels cleaner than the single global namespace option but I'm worried that this alternative solution could get messy if I ended up needing to tie more than one View to ContactsView. I don't know why I would need to tie in more than one View but it's still a potential mess waiting to happen, so I think having a single global namespace is the best option (for now).
UPDATE: I've realised I can pass in additional data when creating a View instance and so that's what I've done here...
var contacts_view = new ContactsView({
el: $('#view-contacts'),
collection: contacts,
associated_view: new ContactView({
el: $('#view-contact'),
collection: contacts
})
});

jQuery - Using javascript variable elsewhere

I fear this question may be extremely newbie level, but I am just drawing a blank.
Within the $(document).ready function I have some DatePicker code...
$('#date-view1').datePicker({
selectWeek: true,
inline: true,
startDate: '01/01/1996'
}).bind('dateSelected', function (e, selectedDate, $td) {
$('#date1').val(selectedDate.asString());
var pfb = selectedDate.asString();
});
The part I am struggling with is the var pfb = selectedDate.asString();
I want to use the variable pfb further down my page in a different function called showProjects().
How can I do this? I have tried declaring the variable inside and outside of the $(document).ready function without luck.
Thanks
Declare var pfb before your document ready block. That'll make it available elsewhere on the page. In the document ready you'll be SETTING an already DECLARED variable.
In Javascript you can use global variables to store values which are accessible from anywhere in the page. Of the many ways of doing this is
setting the value using window.pfb = selectedDate.asString();
and accessing it later anywhere with window.pfb
I'm not sure if this is a problem area, but I wouldn't have tried passing pfb as a param in that onclick event - I think that may re-initialise pfb, or create a new var.
If you're creating it globally (not ideal but should work) then you shouldn't need to pass pfb as a param anyway.
Also, it's good practice not to attach events on the elements like that. Ideally - and jQuery makes this very easy - you should have something in your $(document).ready like this:
$(document).ready(function() {
$("#myButton").click(function() {
showProjects();
});
});
Or even shorten this to
$(document).ready(function() {
$("#myButton").click(showProjects());
});
if you know that showProjects() is the only call that you want to make.
It should work if you just drop the word var
Declaring variables without var makes them global.
It would probably be better form to declare it before the ready block as Dan Ray suggested, but you said you had a hard time with this? Not sure why you would.

Categories