Delay Loading Custom Bindings - javascript

I'm working on an Single Page Application and we're using Knockout quite extensively. We've currently got a list of item that can be clicked, and upon doing so they'll load some content into a modal container. The image below illustrates the different items that'll trigger various content to be displayed:
The content of these containers differs substantially, and can have many different custom bindings spread over several tabs. The items in the image are fairly simple and just use Knockout Components but when we start displaying the modal contents they are much more heavy on the JavaScript hence using bindings.
I've recently added in lazy loading of the JavaScript and HTML templates required by the components and this has worked really well. I've had to use a custom component loader as for various reasons we don't want to use require or similar AMD module loader.
Now I'm faced with the same issue with custom knockout bindings, as I expect we could end up with 100 hundred bindings quite easily as this product expands. Unfortunately there doesn't seem to be an obvious way to load custom bindings in a lazy way like components though, and I'm trying to figure out if there's a way to do this, and what the best way would be. Note that I also don't know the name of the binding up front all of the time, sometimes I may wish to load them dynamically based on the name of an observable.
The only things I've managed to find of note so far, are that there is a ko.getBindingHandler() function which can be overridden, but it requires a synchronous load of a binding handler.
I have thought of an approach to try and do this, but it uses components and feels like a really backward way of achieving my end goal. It'd be something like this:
Replace a usual custom binding:
<div data-bind="lineChart: $data"/>
with
<div data-bind="component { name: compName, params: { vm: $data } }"/>
I'd then use a custom component loader, which is actually just loading the binding handler JavaScript, and writing out essentially a placeholder div with the custom binding in:
var bindingLoader = {
getConfig: function(name, callback) {
if(name.startsWith("binding-")) {
callback({ binding: name.replace("binding-", ""), jsUrl: "/bindings/" + name });
return;
}
callback(null);
},
loadComponent(name, componentConfig, callback) {
var obj = { };
obj.template = '<div data-bind="' + componentConfig.name + ': $data"/>';
$.ajax({ url: componentConfig.jsUrl, dataType: "text" })
.done(function(data)) {
(new Function(data))();
callback(obj);
});
}
}
I'm sure however there must be a better way of achieving this, but I can't think of any other options right now.

I've also answered this question on Github.
#Jeroen is right that there's no built-in way to asynchronously load custom bindings. But any binding can "lazily" perform its own action, which is what the component binding does. By overwriting ko.getBindingHandler, we can detect bindings that haven't yet been loaded, and start the loading process, then return a wrapper binding handler that applies the binding once it's loaded.
var originalGetBindingHandler = ko.getBindingHandler;
ko.getBindingHandler = function (bindingKey) {
// If the binding handler is already known about then return it now
var handler = originalGetBindingHandler(bindingKey);
if (handler) {
return handler;
}
if (bindingKey.startsWith("dl-")) {
bindingKey = bindingKey.replace("dl-", "");
if (ko.bindingHandlers[bindingKey]) {
return ko.bindingHandlers[bindingKey];
}
// Work out the URL at which the binding handler should be loaded
var url = customBindingUrl(bindingKey);
// Load the binding from the URL
var loading = $.getScript(url);
return ko.bindingHandlers["dl-" + bindingKey] = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// Once the binding is loaded, apply it to the element
loading.done(function() {
var binding = {};
binding[bindingKey] = valueAccessor;
ko.applyBindingAccessorsToNode(element, binding);
});
// Assumes that all dynamically loaded bindings will want to control descendant bindings
return { controlsDescendantBindings: true };
}
}
}
};
http://jsfiddle.net/mbest/e718a123/

AFAIK: No, there is no generic way to lazily load custom bindings.
There are however a lot of options, but we can not recommend any specific one because they'll heavily depend on context. To summarize a few examples:
If possible you can use those bindings inside components, and lazily load the components;
Depending on what your binding handler does, it can itself delay loading until the latest needed time (e.g. in the init you'll merely register an event callback that will actually load the things you want to load);
If you properly use if bindings, any custom bindings inside of that will not be evaluated until needed. The same for foreach bindings, which will not apply custom bindings for array items unless those items are there.
You can call applyBindings to specific parts of the DOM only when you're ready to do so.
Et cetera. But again, your question borders on being too broad. Create one (or more?) new questions with actual scenario's, tell us why / how you'd need your custom binding to load lazily, and tell us what approaches you've tried and why they didn't work.

Related

Adding an event handler inside a knockoutjs custom binding

I'm a fairly experienced knockout user, so I understand quite a bit of the under the hood stuff, I have however been battling now for a few days trying to figure out how to achieve a given scenario.
I have to create a system that allows observable's within a given knockout component to be able to translate themselves to different languages.
to facilitate this, I've created a custom binding, which is applied to a given element in the following way.
<p data-bind="translatedText: {observable: translatedStringFour, translationToken: 'testUiTransFour'}"></p>
This is in turn attached to a property in my knockout component with a simple standard observable
private translatedStringFour: KnockoutObservable<string> = ko.observable<string>("I'm an untranslated string four....");
(YES, I am using typescript for the project, but TS/JS either I can work with.....)
With my custom binding I can still do 'translatedStringFour("foo")' and it will still update in exactly the same way as the normal text binding.
Where storing the translations in the HTML5 localStorage key/value store, and right at the beginning when our app is launched, there is another component that's responsible, for taking a list of translation ID's and requesting the translated strings from our app, based on the users chosen language.
These strings are then stored in localStorage using the translationToken (seen in the binding) as the key.
This means that when the page loads, and our custom bind fires, we can grab the translationToken off the binding, and interrogate localStorage to ask for the value to replace the untranslated string with, the code for our custom binding follows:
ko.bindingHandlers.translatedText = {
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
},
update: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Ask local storage if we have a token by that name
var translatedText = sessionStorage[translationToken];
// Check if our translated text is defined, if it's not then substitute it for a fixed string that will
// be seen in the UI (We should really not change this but this is for dev purposes so we can see whats missing)
if (undefined === translatedText) {
translatedText = "No Translation ID";
}
associatedObservable(translatedText);
ko.utils.setTextContent(element, associatedObservable());
}
}
Now, thus far this works brilliantly, as long as the full cache of translations has been loaded into localStorage, the observables will self translate with the correct strings as needed.
HOWEVER......
Because this translation loader may take more than a few seconds, and the initial page that it's loading on also needs to have some elements translated, the first time the page is loaded it is very possible that the translations the UI is asking for have not yet been loaded into into localStorage, or may be in the process of still loading.
Handling this is not a big deal, I'm performing the load using a promise, so the load takes place, my then clause fires, and I do something like
window.postMessage(...);
or
someElement.dispatchEvent(...);
or even (my favorite)
ko.postbox.publish(...)
The point here is I have no shortage of ways to raise an event/message of some description to notify the page and/or it's components that the translations have finished loading, and you are free to retry requesting them if you so wish.
HERE IN.... Lies my problem.
I need the event/message handler that receives this message to live inside the binding handler, so that the very act of me "binding" using our custom binding, will add the ability for this element to receive this event/message, and be able to retry.
This is not a problem for other pages in the application, because by the time the user has logged in, and all that jazz the translations will have loaded and be safely stored in local storage.
I'm more than happy to use post box (Absolutely awesome job by the way Ryan -- if your reading this.... it's an amazingly useful plugin, and should be built into the core IMHO) but, I intend to wrap this binding in a stand alone class which I'll then just load with requireJs as needed, by those components that need it. I cannot however guarantee that postbox will be loaded before or even at the same instant the binding is loaded.
Every other approach i've tried to get an event listener working in the binding have just gotten ignored, no errors or anything, they just don't fire.
I've tried using the postmessage api, I've tried using a custom event, I've even tried abusing JQuery, and all to no avail.
I've scoured the KO source code, specifically the event binding, and the closest I've come to attaching an event in the init handler is as follows:
init: (element: HTMLElement, valueAccessor: Function, allBindings: KnockoutAllBindingsAccessor, viewModel: any, bindingContext: KnockoutBindingContext) => {
// Get our custom binding values
var value = valueAccessor();
var associatedObservable = value.observable;
var translationToken = value.translationToken;
// Set up an event handler that will respond to events on session storage, by doing this
// the custom binding will instantly update when a key matching it's translation ID is loaded into the
// local session store
//ko.utils.registerEventHandler(element, 'storage', (event) => {
// console.log("Storage event");
// console.log(event);
//});
ko.utils.registerEventHandler(element, 'customEvent', (event) => {
console.log("HTML5 custom event recieved in the binding handler.");
console.log(event);
});
},
None of this has worked, so folks of the Knockout community.....
How do I add an event handler inside of a custom binding, that I can then trigger from outside that binding, but without depending on anything other than Knockout core and my binding being loaded.
Shawty
Update (About an hour later)
I wanted to add this part, beacuse it's not 100% clear why Regis's answer solves my problem.
Effectively, I was using exactly the same method, BUT (and this is the crucial part) I was targeting the "element" that came in as part of the binding.
This is my mind was the correct approach, as I wanted the event to stick specifically with the element the binding was applied too, as it was said element that I wanted to re-try it's translation once it knew it had the go-ahead.
However, after looking at Regis's code, and comparing it to mine, I noticed he was attaching his event handlers to the "Window" object, and not the "Element".
Following up on this, I too changed my code to use the window object, and everything I'd been attempting started to work.
More's the point, the element specific targeting works too, so I get the actual event, on the actual element, in the actual binding that needs to re-try it's translation.
[EDIT: trying to better answer the question]
I don't really get the whole point of the question, since I don't see how sessionStorage load can be asynchronous.
I supposed therefore sessionStorage is populated from som asynchronous functions like an ajax call to a translation API.
But I don't see what blocks you here, since you already have all the code in your question:
var sessionStorageMock = { // mandatory to mock in code snippets: initially empty
};
var counter = 0;
var attemptTranslation = function() {
setInterval(function() { // let's say it performs some AJAX calls which result is cached in the sessionStorage
var token = "token"; // that should be a collection
sessionStorageMock[token] = "after translation " + (counter++); // we're done, notifying event handlers
window.dispatchEvent(new Event("translation-" + token));
}, 500);
};
ko.bindingHandlers.translated = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = valueAccessor();
var token = val.token;
console.log("init");
window.addEventListener("translation-" + token, function() {
if (token && sessionStorageMock[token]) {
val.observable(sessionStorageMock[token]);
}
});
}
};
var vm = function() {
this.aftertranslation = ko.observable("before translation");
};
ko.applyBindings(new vm());
attemptTranslation();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="translated: { observable: aftertranslation, token: 'token' }, text: aftertranslation" />

$.empty() vs Backbone's View.remove()?

I understand that when a view is removed through .remove(), .stopListening() is called on that view to remove any event listeners associated with that view in Backbone. From the Backbone docs:
remove view.remove()
Removes a view from the DOM, and calls stopListening to remove any bound events that the view has listenTo'd.
I have views that are appended to a container that only have events related to dom actions on themselves through Backbone's events hook.
var View = Backbone.View.extend({
events : {
'input keyup' : 'searchDropdown'
},
searchDropdown: function () {
$('dropdown').empty();
//Appends views based on search
}
});
My question is really whether or not I'm leaking any memory (significant or not) when calling $.empty() on a container that effectively removes the view(s) appended inside of it. And if I am, is there any good convention for accessing and calling .remove() on those views?
You don't need any special framework for this but it's a good idea to implement removal properly and not depend on the browser being smart enough to do this. Sometimes in a large app you will find you specifically need to override the remove method to do some special cleanup - for instance you are using a library in that view which has a destroy method.
A modern browser tends to have a GC which is smart enough for most cases but I still prefer not to rely on that. Recently I came on to a project in Backbone which had no concept of subviews and I reduced the leaking nodes by 50% by changing to remove from empty (in Chrome 43). It's very hard to have a large javascript app not leak memory, my advice is to monitor it early on: If a DOM Element is removed, are its listeners also removed from memory?
Watch out for things which leak a lot of memory - like images. I had some code on a project that did something like this:
var image = new Image();
image.onLoad(.. reference `image` ..)
image.src = ...
Basically a pre-loader. And because we weren't explicitly doing image = null the GC never kicked in because the callback was referencing the image variable. On an image heavy site we were leaking 1-2mb with every page transition which was crashing phones. Setting the variable to null in a remove override fixed this.
Calling remove on subviews is as easy as doing something like this:
remove: function() {
this.removeSubviews();
Backbone.View.prototype.remove.call(this);
},
removeSubviews: function() {
if (!_.isEmpty(this.subViews)) {
_.invoke(this.subViews, 'remove');
this.subViews = [];
}
}
You just need to add your subview instances to an array. For example when you create a subview you could have an option like parentView: this and add it to the array of the parent. I have done more intricate subview systems in the past but that would work fine. On initialize of the views you could do something like:
var parentView = this.options.parentView;
if (parentView) {
(parentView.subViews = parentView.subViews || []).push(this);
}

Creating a jQuery "object" that is available between _layout and views in ASP.NET MVC

I am coding in ASP.NET MVC 5.2, and using jQuery as my primary script library. I am having a bit of a problem though, with the disparity between _Layout and views that use that layout.
Essentially, it goes like this
_Layout has some script that needs to run (initial wiring, progress bar, splash screen, etc)
Inheriting View has some script that needs to run (unique to that view)
_Layout has additional scripts that need to run after the view's unique scripts.
I have been trying a lot of ways to solve this, but it is actually proving to be a big problem. I have been frequently told that I should not create objects on the global namespace, so I am wondering if there are any other options to creating a script object that I can access in both views that isn't as damaging as global objects.
I have tried promises, and that is getting frustrating. I have tried events, and that doesn't really help because I cannot figure out what to attach the events to. I am told not to attach them to $(document), but that is really one of the only things that will be shared between the view and the layout.
I understand that global objects are not considered good in javascript, but at this point I'm not sure what other options I have to make sure things execute in the right order.
Update
The issue is more about "tooling" than it is about run time. It is true that when the actual view loads and runs, it is all pressed into one big happy page, and would work just fine. The issue is mostly that I have to split up the logic in the tooling (Visual Studio) to keep it from throwing errors and getting confused.
So I suppose it is more accurate to say it is a pseudo-problem.
I have attempted to split up the logic like this, but I think this is just another way of declaring a global object. I got the idea from the Q.js library.
Tasks.js
(function(definition) {
// assign the task system
tasks = definition();
})(function() {
var list = [];
function tasks() {
};
tasks.start = start;
tasks.enqueue = enqueue;
/*
* start the task queue.
*/
function start() {
// make sure to raise a started event for things that need
// to monitor it.
$(this).trigger("started");
};
function enqueue(f) {
// add the potential function to the queue to be processed later.
list.push(f);
$(this).trigger("enqueue", { item: f });
};
return tasks;
});
example usage
$(function(){
$(tasks).on("started", function(){
console.log("event called");
});
console.log("tasks", tasks);
tasks.start();
});
There are a number of ways you could go about this:
Use RequireJs to define Tasks as a module, then:
require(['tasks'], function(tasks){
$(tasks).on("started", function(){
console.log("event called");
});
console.log("tasks", tasks);
tasks.start();
});
Use a global object, but namespace it:
Ciel = Ciel || {};
Ciel.tasks = Ciel.tasks || function(){
var list = [];
...
};
Tie your data to a specific dom element:
<div class="ciel-tasks"></div>
...
$(function() { $('.ciel-tasks').each(function() {
var tasks = $(this);
...
});
It's not really clear what you're describing. From JavaScript's perspective there's no such thing as "_Layout" and "Inheriting View." There's only the resulting DOM delivered to the browser. Any JavaScript code within that DOM can operate on anything else in that DOM. So I'm not sure what any of this has to do with global namespace, events, $(document), etc. Perhaps you're overcomplicating the issue by assuming disparity between your views when, client side, no such disparity exists?
_Layout has additional scripts that need to run after the view's unique scripts.
This sounds like it's just a matter of providing callbacks for operations so that they internally execute in the correct order. For example, if the desired order is:
Layout executes initializeLayout()
View executes initializeView()
Layout executes completeLayout()
Then you can pass these to one another as callbacks and the functions can internally execute those callbacks. So in your Layout you might have something like this at the very top (such as in the header, as long as it's before the view is rendered):
<script type="text/javascript">
function initializeView(){} // placeholder for view-specific initialization
</script>
Then at the bottom with the rest of your scripts:
initializeLayout();
initializeView(completeLayout);
What this does is provide your views with an opportunity to overwrite that initializeView function. If the view defines its own function called initializeView then that one will be executed instead of the placeholder one defined in the layout (remembering that the layout and the view are all one page to JavaScript).
(This also assumes you've elsewhere defined a completeLayout function, since that's what you want to execute after the view is initialized.)
Then in your view you can define that overwriting function:
function initializeView(callback) {
// do some stuff...
if (typeof callback == 'function') {
callback();
}
}
That will execute your view initialization code and then when it's complete will invoke the callback which was provided by the layout, so the layout will then execute its post-view-initialization code. (Naturally, if any of this "initialization" code is asynchronous, you'll want to invoke callbacks in response to those asynchronous callbacks, etc.)

Tracing knockout events

I have a jQuery grid plugin I am creating based on KnockoutJS 2.2.1. So far it is coming along well, but when the plugin is initialized on an element, the 'computed' loadGrid method invokes 3 times.
Just for a little context I am including the loadGrid method and some other related code. (The actual plugin is quite large so for brevity I only am including part of the plugin)
function GridDataModel() {
var self = this;
self.gridState = {
currentPage: ko.observable(opts.gridState.currentPage),
pageSize: ko.observable(opts.gridState.pageSize),
totalPages: ko.observable(opts.gridState.totalPages),
orderBy: ko.observable(opts.gridState.orderBy),
};
self.loadGrid = ko.computed({
read: function () {
console.log('load grid');
if (opts.dataUrl != '') {
var requestData = self.gridState;
if (self.columns.length == 0) requestData.needColumns = true;
$.getJSON(opts.dataUrl, requestData, function (data, textStatus, jqXHR) {
self.loadData(data);
});
}
},
owner: this,
deferEvaluation: false
});
}
gridDataModel = new GridDataModel();
ko.applyBindings(gridDataModel);
Notice the only dependency this computed has is on self.gridState which isn't changing to my knowledge.
I need to determine what is causing the initialization to call the load 3 times. I know loadGrid gets called when defined (b/c deferEvaluation == false), but I need to find out what is causing the other two events to fire.
So for the question...What is a way to trace what event causes a computed to reevaluate?
On another note, I set deferEvaluation : true but when I issue
gridDataModel.gridState.currentPage.valueHasMutated()
The computed does not fire. So the only way I can even get the computed to work is if deferEvaluation == false.
Chrome developer tools on the 'Sources' tab might be able to help. Just check out the panels on the right that will let you set breakpoints on various DOM elements.
See this overview of the scripts panel (now named the 'Sources' panel) or this overview of creating breakpoints on DOM events for more help.
I use the knockoutjs chrome plugin and I use messages for KO, that way you can display stuff to the console. Example of what I did in the past.
self.messages.push(response.msg);

Is there a better way/pattern to write this jQuery plugin?

I have a search plugin that is decently complex: it has different versions of UI and functionality as well as a bunch in interdependent domElements. Multiple instances of the plugin will exist on a page at once.
I am using the basic jQuery authoring pattern: http://docs.jquery.com/Plugins/Authoring
In order to save the options, interdependent events and and all sorts of dom lookups across multiple objects, I've come to passing the element in question to every function, and storing state/options/interdependencies in a data attribute which I retrieve each time. It works, and keeps events from colliding, but it seems like a messy way to write code.
What is the best way to store state across multiple instances? Is the way I am doing it a huge overkill and I am missing something? It probably stems from my misunderstanding of creating class like objects in a jQuery plugin pattern.
(function($) {
var _options = {};
var methods = {
init: function(options) {
return this.each(function() {
if (options) {
_options = $.extend($.fn.examplePlugin.defaults, options);
} else {
_options = $.fn.examplePlugin.defaults;
}
$this = $(this);
var data = $this.data('examplePlugin');
if (!data) {
$this.data('examplePlugin', {
target: $this
});
$.each(_options, function(key, value){
$this.data('examplePlugin')[key] = value;
});
data = $this.data('examplePlugin');
}
//Cache dom fragment plugin is in (if passed)
if (data.domContextSelector == null || data.domContextSelector == "") {
data.domContext = $(body);
} else {
data.domContext = $(data.domContextSelector);
}
init($this);
});
}
};
var init = function(element) {
data = getData(element);
//Storing dom elements to avoid lookups
data.relatedElement = $(data.relatedElementSelector, data.domContext);
element.click(function(event){
doSomethingCool($(event.currentTarget));
});
};
var doSomethingCool = function(element) {
data = getData(element);
element.slideUp();
data.relatedElement.slideDown();
};
var adjustHeight = function(element) {
data = getData(element);
element.height(data.relatedElement.height());
};
var getData = function(element) {
return $(element).data('examplePlugin');
};
$.fn.examplePlugin = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
}
else {
$.error('Method ' + method + ' does not exist on jQuery.examplePlugin');
}
return false;
};
$.fn.examplePlugin.defaults = {
defaultA: 'something',
relatedElementSelector: '#related',
domContextSelector: 'header.header'
};})(jQuery);
Yup, if you follow the jQuery guide, you are building it according to how it's supposed to be built and taking advantage of what it was designed to do (especially chaining).
However, I don't necessarily follow that path. There are a lot of ways you can do these plugins, take for example this guy who made a boilerplate for jQuery plugins which are NOT based on jQuery's design but rather in the OOP perspective (which I prefer). I see it as cleaner, but has the sacrifice of not following the usual syntax (the element.myPlugin({options}) and not being able to chain (until you modify a bit)
The same guy has an older post which is a boilerplate for the usual jQuery plugin design.
I've found your tweet when checking how my plugin saves a state, while learning plugin developing along this tutorial:
http://tutsplus.com/lesson/head-first-into-plugin-development/
In this massive lesson, we’ll dive into jQuery plugin development.
Along the way, we’ll review various best practices and techniques for
providing the highest level of flexibility for the users of your
plugins.
Personally, I suggest sticking to what the jQuery team recommends, in terms of plugin design patterns. It helps keeps consistency, and makes your plugin more community friendly.
Having said that...
I've run into the problem of trying to keep the state of multiple elements as well. One solution I've found is to use the jQuery Data API (which looks like this: $( selector ).data( key, value ) ) to keep meta information like an element's state or the application state.
The nice thing about using data() is that it's not updating/acessing the DOM, rather it's using jQuery's internal meta stuff, so it's faster to access than trying to store info hidden input fields, changing class names, or doing other funky tricks that developers have tried to use to store data on the clientside. ( Keep in mind too that you don't need to use the HTML5 doctype to use the data API, but if you do data-*key attributes are extremely helpful! )
It gets tricky when all the elements have their own states but the current element is the one that is controlling the overall plugin state. For this scenario I use the body tag to store data bout the current element, something like this:
$('body').data('myPluginNameSpace.current', selectorRef );
That way, when I need to check the state of my plugin/page/application, or listen for my plugin-specific event that's bubbled up to the document object, I can do a quick lookup for the current/selected element, and apply any UI changes or behaviors to it:
var currentElementRef = $('body').data('myPluginNameSpace.current');
doFunStuff( currElementRef );
There are a number of other ways you can do this too, like creating a custom Event object and attaching custom parameters to it:
var myPluginEvent = jQuery.Event( 'customEvent.myPluginNameSpace', { myProp : myValue });
$( document ).trigger( myPluginEvent );
When your custom Event gets triggered and later handled via a callback function, your custom parameters are attached to the Event Object passed to the handler:
$( document ).on( 'customEvent.myPluginNameSpace', function( e ){
doStuff( e.myProp ); //you can access your custom properties attach to the event
});
You can get to the same destination via many, different roads; that's the beauty and horror of JavaScript.
In your particular case keep in mind that you don't have to have everything running inside return this.each({ }) portion of the methods.init function for your plugin:
For example, unless you are setting specific options for each element, I would take out the part where you're extending the options object for every element!
var methods = {
init: function(options) {
//DO OPTIONS/EVENTLISTENER/etc STUFF OUT HERE
return this.each(function() {
//DONT DO THIS
if (options) {
_options = $.extend($.fn.examplePlugin.defaults, options);
} else {
_options = $.fn.examplePlugin.defaults;
}
Try this instead:
...
var methods = {
init : function( options ){
//do setup type stuff for the entire Plugin out here
var _options = $.MyPlugin.options = $.extend( defaults, options );
//add some listeners to $(document) that will later be handled
//but put them in an external function to keep things organized:
//methods.addListeners()
//this refers to the array of elements returned by $(selector).myPlugin();
//this.each() iterates over, EACH element, and does everything inside (similar to Array.map())
//if the selector has 100 elements youre gonna do whats in here 100 times
return this.each(function(){
//do function calls for individual elements here
});
},
Also, taking advantage of custom events will help you! Add some event listeners to the document object, and let the event handlers figure out which element to interact with using the data API or custom event parameters.

Categories

Resources