From: http://developers.facebook.com/docs/guides/web/
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
2 questions:
Why is the code used to load the FB SDK inside a self-invoking closure?
What part of the code actually makes the SDK load asynchronously?
The function is self calling so as to not polute the global namespace. It could have very eaisly have been written as follows:
function loadScript(d, s, id){
//...
}
loadScript(document, 'script', 'facebook-jssdk');
But then you've got an object called loadScript floating around at the global scope level, which would conflict with any other libraries that also have a variable or function called loadScript.
It's asynchronous because it inserts a script element into the DOM dynamically, which is an asynchronous operation. Take a look at this article for a deeper discussion, http://friendlybit.com/js/lazy-loading-asyncronous-javascript/
Also, that's not a closure, as there isn't a function inside the function. A closure is a function that maintains state of it's environment. In any event, that distinction isn't hugely important for this question.
Related
I totally get why Turbolinks 5 is awesome and if you're reading it, you probably do as well, but I am very frustrated with how badly it plays with the other scripts on the block.
To date, there is no simple explanation (human readable) that shows how to wrap existing jQuery scripts in a way that would allow them to function.
Take for example this one: https://github.com/Bttstrp/bootstrap-switch. It's well written, simple to understand. You load the js and css to your assets pipeline and instantiate it on some page.
# view.html.erb
<input type="checkbox" class="switch"> switch button
<script type="text/javascript">
$(".switch").bootstrapSwitch();
</script>
you go to view.html, click another page, click back and you see two buttons.
Next, you spend 5 hours looking for a way to have Turbolinks load the instance of bootstrapSwitch only once if not loaded before. Well, even if you do, the functionality will be gone. Clicking it will not work.
$(document).on("turbolinks:load", function()... will load it on every Turbolink visit, and for now, the only way I could make it work and not create duplicates was to disable cache on view.html with
<%= content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
Which feels kinda stupid.
I think it all has something to do with using idempotent - https://github.com/turbolinks/turbolinks#making-transformations-idempotent but how do you practically do this?
Could someone please take this simple plugin as an example and share a simple, elegant solution for making it work which we can then reproduce with other scripts?
Developing apps with Turbolinks does require a particular approach in order to get things running smoothly. Due to differences the way pages are loaded and cached, some patterns of running scripts won't behave in the same way with Turbolinks vs. without. This may seem unfriendly at first, and the "gotchas" can be frustrating, but I've found that with a little understanding, it encourages more organised, robust code :)
As you have figured out, the problem with duplicate switches is that the plugin is being called more than once on the same element. This is because Turbolinks caches a page just before navigating away from it, and so the cached version includes any dynamically added HTML[1] e.g. stuff added via plugins. When navigating back/forward, the cached version is restored, and the behaviour is duplicated :/
So how to fix this? When working with code which adds HTML or event listeners, it is generally a good idea to teardown behaviours before the page is cached. The Turbolinks event for that is turbolinks:before-cache. So your setup/teardown might be:
// app/assets/javascripts/switches.js
$(document)
.on('turbolinks:load', function () {
$('.switch').bootstrapSwitch()
})
.on('turbolinks:before-cache', function () {
$('.switch').bootstrapSwitch('destroy')
})
This is a bit difficult to test since all the setup and teardown is done in event handlers. What's more, there maybe many more cases like this, so to prevent repitition, you may want to introduce your own "mini-framework" for setting up and tearing down functionality. The following walks through creating a basic framework.
Here's is what we'll aim for: calling window.App.addFunction with a name and a function registers a function to call. That function gets the elements and calls the plugin. It returns an object with a destroy function for teardown:
// app/assets/javascripts/switches.js
window.App.addFunction('switches', function () {
var $switches = $('.switch').bootstrapSwitch()
return {
destroy: function () {
$switches.bootstrapSwitch('destroy')
}
}
})
The following implements addFunction, storing added functions in the functions property:
// app/assets/javascripts/application.js
// …
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
}
}
We'll call each function when the app initializes, and store the result of each function call in the results array if it exists:
// app/assets/javascripts/application.js
// …
var results = []
window.App = {
// …
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
}
}
Tearing down the app involves destroying calling destroy (if it exists) on any results:
// app/assets/javascripts/application.js
// …
window.App = {
// …
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
Finally we initialise and teardown the app:
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
So to put this all together:
;(function () {
var results = []
window.App = {
functions: {},
addFunction: function (name, fn) {
this.functions[name] = fn
},
init: function () {
for (var name in this.functions) {
var result = this.functions[name]()
if (result) results.push(result)
}
},
destroy: function () {
for (var i = 0; i < results.length; i++) {
var result = results[i]
if (typeof result.destroy === 'function') result.destroy()
}
results = []
}
}
$(document)
.on('turbolinks:load', function () {
window.App.init.call(window.App)
})
.on('turbolinks:before-cache', window.App.destroy)
})()
Functions are now independent of the event handler that calls them. This decoupling has a couple of benefits. First it's more testable: functions are available in window.App.functions. You can also choose when to call your functions. For example, say you decide not to use Turbolinks, the only part you'd need to change would be when window.App.init is called.
[1] I think this is better than the default browser behaviour (where pressing "Back" returns the user back to the page as it was when it was first loaded). A Turbolinks "Back" returns the user back to the page as they left it, which is probably what a user expects.
I have a knockout custom binding that wraps functionality to an image crop library (https://github.com/fengyuanchen/cropper .) I’m catching the cropend.cropper event to (eventually) attach the cropped output to an observable.
I’m using:
knockout 3.3
jquery 2.1.4
cropper 2.0
Here is the binding handler:
ko.bindingHandlers.cropper = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var value = ko.unwrap(valueAccessor());
var a = 1;
ko.utils.registerEventHandler(element, "cropend.cropper", function (event) {
var previewOutputObservable = allBindings.get('previewOutput');
var valueAccessorFromAllBindings = allBindings.get('cropper');
var b = 1;
});
$element.cropper(value);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var value = ko.unwrap(valueAccessor());
var c = 1;
}
};
And here is the element I'm binding to:
<img class="img-responsive" data-bind="attr: {'src': sampleObservable}, cropper: { aspectRatio: 16/9 }, previewOutput: cropPreview "/>
When I put a breakpoint (in chrome) on var b = 1; none of the parameters in the init are defined except allBindings. I’ve seen several examples use this general pattern though (e.g. here). What am I doing wrong?
The outer variables are accessible through a closure context. Chrome tries to optimize the context by only including variables that are actually accessed in the closure code. Since element wasn't accessed in your code, it's not part of the context. This is a good feature as it means that variables that aren't used in any closure can be safely disposed.
This is either an issue with the Chrome debugging tools or a 'feature' of knockout.
Before var b = 1 I added the line var newValue = element. I put a breakpoint on that line and lo and behold the element parameter now has values.
It seems like the variables aren't initialized until they're used in the current context.
I am using angularJS in my MVC application and loading a partial using ajax call in angularJS. I am using $compile to compile the html. Everything is working fine on local but it is not working on production. Unexpected error is showing. Below is the code I am using.
Angular Controller:
app.controller("mproductController", ["$scope", "mproductService", "$compile", function ($scope, mproductService, $compile) {
$scope.ShowProdUnitPop = function (val) {
mproductService.ShowProdUnitPop(val).success(function (result) {
debugger;
var snippet = angular.element(result);
$compile(snippet)($scope);
$("#dvAddProd").html(snippet);
$("#dvPopup").modal('show');
});
}
}
Angular Service:
app.service("mproductService", ["$http", function ($http) {
this.ShowProdUnitPop = function () {
var request = $http({
method: "post",
url: "/Home/GetActiveCat"
});
return request;
}}
MVC Controller:
[HttpPost]
public ActionResult AddProduct(int id)
{
ViewBag.ProductCategory = Utility.GetProd(id);
return PartialView("_AddProduct",new Product());
}
Error is throwing on this line:
var snippet = angular.element(result);
$compile(snippet)($scope);
$("#dvAddProd").html(snippet);
$("#dvPopup").modal('show');
It is working on local but not on production. Please help.
Seems like you are using bundling and minification enabled on production server. That is breaking you controller code while you are utilizing that controller and its necessary to have inline array annotation of DI while minifying JS files.
Most possible answer would be you are using $compile by injecting it to controller and using it, That's fine. It seems like you had not followed inline array dependency injection inside your controller.
Also you need to attach compile element to your dvAddProd element, rather than updating html, appending html to DOM will never make angular angular binding on that element. Looks like this code shouldn't be working on any of the environment if it has angular bindings on it.
Controller
app.controller('someCtrl', ['$scope', '$http', '$compile',
function($scope, $http, $compile) {
//you should have controller in this way that would fix your issue
//note I'm using inline array notation of DI
$scope.ShowProdUnitPop = function(val) {
mproductService.ShowProdUnitPop(val).success(function(result) {
debugger;
var snippet = angular.element(result);
var compiledSnippet = $compile(snippet)($scope);
$("#dvAddProd").append(compiledSnippet); //should attach compile element
});
}
}
]);
Visit this link for more Information
I am building a Single Page Application using ASP.NET and sammy.js, where all views except for the Home/Index view are rendered as partial views so that sammy can swap out the content of the main body with the partial view that is returned.
I am using the example given here, and everything loads fine as expected.
Similar to the above example, in my Home/Index page I have reference to a script called routing.js, which wraps the sammy function call in order to parse the MVC route:
var Routing = function (appRoot, contentSelector, defaultRoute) {
function getUrlFromHash(hash) {
var url = hash.replace('#/', '');
if (url === appRoot)
url = defaultRoute;
return url;
}
return {
init: function () {
Sammy(contentSelector, function () {
this.get(/\#\/(.*)/, function (context) {
var url = getUrlFromHash(context.path);
context.load(url).swap();
});
}).run('#/');
}
};
}
I need to call a callback function after the content swap has fully completed in order to implement further jQuery functionality on the newly rendered content. My dilemma is that no matter what option I try from the sammy.js docs, nothing seems to run the callback after the content has been swapped.
I have tried all of the following (all "valid" ways of passing a callback according to the sammy.js docs):
content.load(url).swap(pageLoadScripts(url));
content.load(url).swap().onComplete(pageLoadScripts(url));
content.load(url).swap().then(pageLoadScripts(url));
content.load(url).swap().next(pageLoadScripts(url));
content.load(url,pageLoadScripts(url)).swap();
and even
content.load(url).swap();
pageLoadScripts(url);
In every case the pageLoadScripts function fires off prior to the content being swapped. Any ideas or suggestions on what to do differently?
This is a bit of a hack, but it works.
Inside the Sammy initialization function, I added the following override to the swap function just before the override to the get function:
this.swap = function (content, callback) {
var context = this;
context.$element().html(content);
pageLoadScripts(hashedUrl);
};
FWIW, I still have not been able to get callback to be anything other than 'undefined', even in this override function.
Managed to get this working:
// override for callback after page load
this.swap = function(content, callback) {
this.$element().html(content);
if (callback) {
callback();
}
};
// users
this.get('/#/users', function(context) {
context.load('/users').swap(function() { replaceBindings(viewModel.users); });
});
I managed to get the callback param to NOT be 'undefined' by wrapping it in another function.
I'm new to JS and I have recently ran into a problem with the setTimeout function while using the Jquery-UI Datepicker.
I would like to get a value from within a function referenced in a setTimeout function and use that value on other areas of my code. However, because setTimeout delays the code execution, I can't seem to assign and use the said value.
Here's part of the code
$calendar.datepicker({
inline: true,
onSelect: function (dateText,inst) {
var startDate;
window.setTimeout(function(){getStartDate();}, 1);
// ... Do something with startDate. No matter how I try, startDate is always undefined.
function getStartDate () {
var r = $calendar
.find('.ui-datepicker-current-day')
.parent()
.find('.selectable')
.first()
.children()
.text();
startDate = new Date(date.setDate(r));
return startDate;
}
},
});
I need to use the setTimeout function otherwise the value returned from Jquery UI datepicker is wrong.
Ideally, I'm looking for startDate to be the value returned from the function getStartDate() that is set within the timeout function.
What I'm writing is obviously wrong and I have no idea how to return the startDate value from within the timeout function and use it elsewhere
Help is very much appreciated. Thanks!