I've been creating reusable components as jQuery plugins for projects for some time now. I like being able to abstract away the logic, and inject all of the context (selectors, options, etc) on a case-by-case basis.
Now, I'm starting to use KnockoutJS, and have written a nice little jQuery plugin that uses Knockout for its internal logic. It works quite well, but I'm wondering if there is a better way to do it? Does Knockout itself have a pattern/convention for creating reusable components, or is this pattern okay?
Here is an example, it should be enough to give you the idea of what I'm doing.
/*globals jQuery, knockout */
(function ($, ko) {
"use strict";
$.fn.itemManager = function (options) {
// set up the plugin options
var opts = $.extend({}, $.fn.itemManager.defaultOptions, options),
$wrap = $(this),
templateUrl = '/path/to/template/dir/' + opts.templateName + '.html';
// set up the KO viewmodel
function ItemManagerModel(items) {
var self = this;
self.items = ko.observableArray(items);
self.chosenItemId = ko.observable();
self.chosenItemData = ko.observable();
// generic method for navigating the Item hierarchy
self.find = function (array, id) {
/* ... */
};
/* bunch of other stuff... */
self.goToItem(items[0]);
}
// this is where the whole thing starts...
$(opts.openTriggerSelector).click(function (e) {
e.preventDefault();
// set the template html
$.get(templateUrl, function (data) {
$wrap.html(data);
});
// initial load and binding of the data, in reality I have some more conditional stuff around this...
// there's probably a better way to do this, but I'll ask in a separate question :)
$.get(opts.getItemsServiceUrl, function (result) {
ko.applyBindings(new ItemManagerModel(result), document.getElementById($wrap.attr('id')));
$wrap.data('bound', true);
});
// opens the template, which now is bound to the data in a dialog
$wrap.dialog({ /* ... */ });
// provide default plugin options
$.fn.itemManager.defaultOptions = {
getItemsServiceUrl: '/path/to/service',
openTriggerSelector: 'a',
templateName: 'Default'
};
} (jQuery, ko));
I run a github project for KO components. It's using an older version of KO and is due for a major revamp but you may be able to get some ideas. I basically do it all through custom bindings that take model objects as their configuration and data.
I am always on the lookout for a better way of doing this. Keep me posted if you come up with a better way.
https://github.com/madcapnmckay/Knockout-UI
Related
I try to work with RequireJS and AMD module definition and have write a module that do my things in object format i think.
(i went from jquery and have not study a OOP javascript well)
myModule.js
define([
jquery,
datepicker,
], function ($, datepicker) {
var myModule = {
debug: true,
lang: 'auto',
initModule: function (element) {
myModule.element = element;
},
// ... other methods
}
return myModule;
});
And it work well, but if i try to use it for more than one elements/time it override him self, and i can't use it more than one time in same page.
main.js
requirejs(['MyModule'],
function (MyModule) {
// all the elements on page
$elements = $('.element');
$elements.each(function () {
MyModule.initModule($(this));
});
});
When i have more than one <div class="element"> on page only the last one work, i think because my module is override him self.
I tried to add new MyModule() but have a error TypeError: MyModule is not a constructor
I think i need to add a constructor or something else, in any case have a instance of my module to use instead of the object (that i think are precompiled by requirejs and returned ready for work). Any helps are good, many thanks in advance!
Ok! For do that! I completely refactor my code, and instead of return a object in my module definition i prototyped a function for get after his instance and i create a constructor for reset the properties/vars:
myModule.js
define([
jquery,
datepicker,
], function ($, datepicker) {
// constructor
var myModule = function () {
// reset lang because it maybe was changed in previous instance,
// i think because javascript share this thing trough objects?
myModule.prototype.lang = 'auto';
}
myModule.prototype.debug = true
myModule.prototype.lang = 'auto';
myModule.prototype.initModule = function (element) {
myModule.element = element;
};
// ... other methods with `myModule.prototype.` prefix
return myModule;
});
Great, now i can call myModule trough new myModuel() syntax and have same functionality for different elements on page.
main.js
requirejs(['MyModule'],
function (MyModule) {
// all the elements on page
$elements = $('.element');
var p = 1, _mod = [];
$elements.each(function () {
_mod[p] = new MyModule();
_mod[p].initModule($(this));
p++;
});
});
This work for me, i not completely understand yet what i do, but my purpose are satisfated, i can reuse same module functionality for different elemnts on page.
Suggest me readings:?
I securely need to read something about OOP Javascript, prototype and how javascript manage instance/class/object/var in memory and namespaces.
I have a js file looking like this:
$(document).ready() {
// Mostly DOM manipulation operations
functionOne();
functionTwo();
functionThree();
...
}
What I want to achieve is to encapsulate and organize all functions in one object, and create logic so they get called only on specific pages. I started writing something like this:
(function( window, document, $, undefined) {
var MyNamespace = {};
// Cache these to local scope
MyNamespace.$window = $(window);
MyNamespace.$document = $(document);
// Functions
MyNamespace.functionOne = function() {
...
};
MyNamespace.functionTwo = function() {
...
};
})( window, window.document, window.jQuery );
I wonder if I am going in the right direction, and if there are any better ways of doing this with the page specific logic that I have not started implementing yet (I have a page identifier already available). I have looked at this book written by Addy Osmani and only thing that looked similar to what I want to achieve was the Command pattern, but I am still not convinced if it would be the right choice.
What you are looking for is kind of a class in the normal OOP sense. I personally use the following schema:
var OPTIONS = {
selectorA: '.js-xxx'
};
var MyClass = function(el) {
this.el = el;
this._init() // private function called
};
MyClass.prototype._init = function () {
// Do some stuff
};
MyClass.prototype.publicFunction = function() {
// Do exposable stuff here
}
window.instance = new MyClass(document.querySelect('#myElement'));
In order to load these modules you should need requireJS or another AMD loader.
First of all I'm pretty new to javascript.
I have wrote a javascript plug-in that filters html records. Using Knockout.js, basically keyword/s are typed into a text box and the plug-in filters through the html records to reveal the records that contain the keyword/s.
I have wrote the javascript using the Module pattern, and would like the opportunity for the .js file to stand alone, whilst also giving the user the opportunity to change ways in which the filter works.
Here is a brief and simple overview of the structure of my .js file:
$(function(){
var s,
Filter = {
settings:{
//default settings
},
initialise: function(){
s = this.settings;
this.someFunction1;
this.someFunction2;
},
someFunction1: function(){
//
},
someFunction2: function(){
//
},
}
Filter.initialise();
});
As you can see, I have specified one function to be called - initialise. Within initialise I have set the variable s (declared at the same level as the module) to point to settings so that all sub functions of the module can access it.
What I want to know is, is it possible for the overriding settings to be passed via the html file that calls the .js file?
Ideally I would like there to be default settings for the plug-in, but give the user the option to specify/override certain settings unique to their need.
Any help would be appreciated. Thanks.
First up, there's a few peculiarities in your code.
One thing is that you have this.someFunction1; (and one for the second function) in your initialize function, but that doesn't quite do anything. It's just a statement.
Another thing is that you declare Filter as an object, but you're missing , (commas) between the various properties.
Finally, you declare someFunction2 twice (though I assume it's an copy/paste error?), and you're missing a semi-colon.
To find these problems you can use a JavaScript linting tool, for example JSHint. Try copy-pasting your question's code there and you'll see all those issues pop up.
If I fix those issues I get to this:
$(function(){
var s,
Filter = {
settings:{
//default settings
},
initialise: function(){
s = this.settings;
},
someFunction1: function(){
//
},
someFunction2: function(){
//
}
};
Filter.initialise();
});
Currently in this code, the Filter is only visible in the outer closure. To make it available to other bits of code you'll have to export it, either to your own namespace object or as a jQuery plugin.
The first option is possible, but will require a bit more work and assumptions about your setup. So let me stick to showing the second option. The jQuery's plugin tutorial is pretty good, but I've based the following code on this blogpost because it's a bit more complete IMO:
(function($) {
$.filter = function(element, options) {
var defaults = {
// default settings
}
var filter = this;
filter.settings = {};
var initialise = function() {
filter.settings = $.extend({}, defaults, options);
filter.element = element;
// code goes here
}
// This function is *public*, just save it in a 'var' instead of setting it
// as a property of 'plugin' to keep it private to your plugin.
filter.someFunction1 = function() {
//
}
filter.someFunction2 = function() {
//
}
initialise();
}
})(jQuery);
In this code the jQuery extend function is used above to extend default settings with user provided options.
You can call your plugin in its most basic form like this:
var options = { filterEmptyOptions: true, showTimeZone: false, /* etc */ };
$.filter($('#myDiv'), options);
Additionally you could save the result of last line and call the plugin's public methods later on.
I need to extend marionette.js classes with some functionality I'd like to have in all classes I create in my app.
What I currently do is to save original method of Marionette and to override it with my own method, calling to original from inside overridden.
For instance:
(function() {
var oldMarionetteItemViewConstructor = Marionette.ItemView.prototype.constructor;
Marionette.ItemView.prototype.constructor = function() {
// Some custom stuff I want to have here
.....
// Call to original constructor
return oldMarionetteItemViewConstructor.call(this, arguments);
}
})();
It seems some hacky and I wonder if there better way?
Marionette hoists the Backbone.View.extend() method (which itself is actually hoisted from Underscore.js) so all you have to do is:
var MyFancyView = Marionette.ItemView.extend({
//define your custom stuff here
});
var MyExtendedView = MyFancyView.extend({
//This view picks up the same props/methods form MyFancyView
});
You're pattern works, but the native #extend() method will keep your prototypes clean:
https://github.com/jashkenas/underscore/blob/master/underscore.js#L838
According to the developer documentation jquery plugins are supposed to have only one namespace for all functions they make available. Which is straight forward as long as you only expose a single function per context (static/element).
(function($){
var
state_a = 0,
$.myplugin = function(in_options) {
// static
return this;
}
$.fn.myplugin = function(in_options) {
// element
return this;
}
})(jQuery);
This makes calls like this possible:
$("elem").myplugin(options);
jQuery.myplugin(options);
What's the best approach if you have more than one function and need to share state? I would like to call into my plugin like this:
$("elem").myplugin.start(options);
$("elem").myplugin.stop();
jQuery.myplugin.start(options);
jQuery.myplugin.stop();
I've used arguments.callee before:
(function ($) {
$.fn.pagination = function (options) {
var defaults = {
};
options = $.extend(defaults, options);
var object = $(this);
arguments.callee.updatePaging = function () {
//do stuff
};
});
Then, on your page:
var pagination = $("#pagination");
pagination.pagination();
pagination.pagination.updatePaging();
jQuery plugins tend to use a string parameter for different functions:
$(...).myplugin('start', options);
$(...).myplugin('stop');
I can't think of an easy way to use the syntax you would like to use, since having an extra property inbetween will make this point to something else than the jQuery object with the selected elements. Personally I find using a string parameter to completely change what the function does somewhat ugly too. $(...).myplugin().function() would be doable, but likely not as efficiently as just using that string parameter.
This is explained well in the docs: http://docs.jquery.com/Plugins/Authoring#Namespacing