Invoke function from jquery plugin boiler plate code - javascript

I have found nice jQuery plugin and it uses boiler plate template. Everything works fine, but I can't invoke internal function to get selected items.
The construction of this plugin is below:
(function ($, window, document) {
'use strict';
// constructor
var SearchableOptionList = function ($element, options) {
this.$originalElement = $element;
this.options = options;
this.metadata = this.$originalElement.data('sol-options');
};
// plugin prototype
SearchableOptionList.prototype = {
DATA_KEY: 'sol-element',
// default option values
defaults: {
...
},
// initialize the plugin
init: function () {
this.config = $.extend(true, {}, this.defaults, this.options, this.metadata);
...
return this;
},
//some functions
...
selectAll: function () {
...
},
deselectAll: function () {
...
},
getSelection: function () {
return this.$selection.find('input:checked');
}
};
// jquery plugin boiler plate code
SearchableOptionList.defaults = SearchableOptionList.prototype.defaults;
window.SearchableOptionList = SearchableOptionList;
$.fn.searchableOptionList = function (options) {
var result = [];
this.each(function () {
var $this = $(this),
$alreadyInitializedSol = $this.data(SearchableOptionList.prototype.DATA_KEY);
if ($alreadyInitializedSol) {
result.push($alreadyInitializedSol);
} else {
var newSol = new SearchableOptionList($this, options);
result.push(newSol);
setTimeout(function() {
newSol.init();
}, 0);
}
});
if (result.length === 1) {
return result[0];
}
return result;
};
}(jQuery, window, document));
Full code you can find on GitHub.
I try to invoke getSelection function like below:
// initialize sol
var s = $('#my-select').searchableOptionList({
maxHeight: '150px',
showSelectAll: true
});
s.selectAll();
I get an error:
TypeError: this.config is undefined
Is it possible to invoke functions using this template of boiler plate?
You can play around on jsfiddle

I believe line 1031 is the culprit
setTimeout(function() {
newSol.init();
}, 0);
Since the init is deferred the code is not ready when you call it right away. The esiest fix is to defer your call too, but there is no gurantee that it will be initialized.
setTimeout(function(){s.selectAll()},1000);
The better solution is to use the plugin's events to hook into when it has been initialized.
$('#my-select').searchableOptionList({
maxHeight: '150px',
events: {
onInitialized: function() {
this.selectAll();
}
}
});
fiddle

Related

How can I extend a JS Component?

I have this custom component
(function ($) {
$.fn.stuff = function (options) {
var myStuff = this;
myStuff.render_item = function () {
// Do something
return "default result";
};
myStuff.test = function () {
myStuff.render_item;
};
return myStuff;
};
}(jQuery));
I want to "extend" this component and re-declare the "render_item" fonction without editing the file directly since it's from a library.
How can I achieve this ?
Thank you.
You can monkey-patch it like this:
(function($) {
$.fn.old_stuff = $.fn.stuff;
$.fn.stuff = function(options) {
var myStuff = $(this).old_stuff(options);
myStuff.old_render_item = myStuff.render_item;
myStuff.render_item = function() {
// do something
myStuff.old_render_item();
// do something else
};
return myStuff;
};
}(jQuery));

JavaScript functions is throwing Uncaught TypeError

I have a web page. In my web page I'm referencing some JavaScript I've written in a file called "spacer.js". spacer.js is more complicated, but the general setup is like this:
function spacer() {
// do stuff
console.log(spacer.options);
}
spacer.initialize = function(options) {
spacer.options = options;
};
Then, in my web page, I have:
<script type="text/javascript" src="./spacer.js"></script>
<script type="text/javascript">
spacer.initialize({ id:1 });
window.onresize = spacer();
</script>
When I load my web page, I get an error that says:
Uncaught TypeError: spacer.initialize is not a function.
I don't understand. What am I doing wrong.
function spacer() {
// do stuff
console.log(spacer.options);
}
spacer.initialize = function(options) { // needed an assignment operator and function keyword
spacer.options = options;
}
To create a method you need to use the function keyword.
To the browser, this
spacer.initialize(options) {
spacer.options = options;
}
is interpreted like this
spacer.initialize(options); // method evocation
{spacer.options = options;}; // anonymous object
Change the way it is defined
//from
spacer.initialize(options) {
spacer.options = options;
}
//to
spacer.initialize = function(options) {
spacer.options = options;
}
In your JS remove the () from window.resize call
spacer.initialize({ id:1 });
window.onresize = spacer;
As an aside it looks like you're trying to do one of two things and sitting uncomfortably in the middle. I'd personally go for 1) in this instance, but probably best to stick with one or the other.
1) Creating an object with methods
var spacer = {};
spacer.initialize = function (options) {
this.options = options;
}
spacer.getOptions = function () {
return this.options;
}
spacer.initialize({ name: 'spacer' });
spacer.getOptions(); // { name: spacer });
DEMO
2) Using a constructor function to build an space object instance:
function Spacer() {}
Spacer.prototype.initialize = function (options) {
this.options = options;
return this;
}
Spacer.prototype.getOptions = function () {
return this.options;
}
var spacer = new Spacer().initialize({ name: 'spacer' });
spacer.getOptions(); // { name: 'spacer' }
DEMO

jQuery plugin - update settings after initialization

I have a jQuery plugin, and I want to be able to change options on the fly, like this example: $('.element').pwstabs('options','effect',scale) or something simular to it. I tried adding update: function, tried adding Plugin.prototype.update, but still cant figure out how to do that :)
Here's the structure of the plugin:
;(function ($, window, document, undefined) {
var pluginName = "pwstabs",
defaults = {
effect: 'scaleout',
defaultTab: 1,
containerWidth: '100%',
tabsPosition: 'horizontal',
horizontalPosition: 'top',
verticalPosition: 'left',
responsive: false,
theme: '',
rtl: false,
controlls: false,
next: '',
prev: '',
first: '',
last: '',
auto: false,
play: '',
pause: ''
};
function Plugin(element, options) {
this.element = $(element);
this.$elem = $(this.element);
this.settings = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function(){
// Here's the code for the plugin
}
};
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
})(jQuery, window, document);
So now I use the plugin like:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
And when I click a button, i want to change effect to lets say scaleout. With code like:
$('.button').click(function(){
$('.element').pwstabs('options','effect','scalein');
});
So how do I implement this in the plugin?
Currently the only supported invocation pattern in that plugin is to send in an object literal containing the settings to overwrite the defaults. E.g.:
$('.element').pwstabs({
effect: 'scalein',
defaultTab: 2
});
That invocation pattern is defined in the following method:
$.fn[pluginName] = function ( options ) {
return this.each(function () {
new Plugin( this, options );
});
};
As you see, a dictionary of options is sent as the only parameter to the constructor function Plugin() to build the plugin and initialize it.
To support the invocation pattern you need, you would have to modify this method to support both invocation patterns (initialization with an object literal, but also invoking any method with more params, like your options setting method).
Here is an improved function that will handle both invocation patterns. In addition it will also store the instance of a plugin on an element, so you can access the existing settings etc. on subsequent invocations (e.g. settings changes) on the same element.
$.fn[pluginName] = function (options) {
// get the arguments
var args = $.makeArray(arguments),
after = args.slice(1);
return this.each(function () {
// check if there is an existing instance related to element
var instance = $.data(this, pluginName);
if (instance) {
if (instance[options]) {
instance[options].apply(instance, after);
} else {
$.error('Method ' + options + ' does not exist on Plugin');
}
} else {
// create the plugin
var plugin = new Plugin(this, options);
// Store the plugin instance on the element
$.data(this, pluginName, plugin);
return plugin;
}
});
}
This would allow you to invoke the plugin as requested:
$('.element').pwstabs('options','effect','slidedown');
However, this implies you have an 'options' method in the Plugin prototype, so make sure to add one:
Plugin.prototype = {
options: function (option, val) {
this.settings[option] = val;
},
// Constructing Tabs Plugin
init: function () {
// omitted code for brevity
}
}
As you see the options settings just sets the new option on the existing instance. Very simple and efficient. The new setting will be picked up by the click method handler and voila!
Here is a jsFiddle with example code in case you have trouble implementing what i was describing so far:
http://jsfiddle.net/7whs3u1n/6/
Update: I have much improved my answer to get rid of unneeded stuff, include more details and a full implementation that works (check the fiddle above) ;) i hope that this answers your question!
Adding statefulness to your plugin wasn't hard, but when you have spare time also check the alternative mechanism for writing stateful jQuery stateful plugins called jQuery widget factory:
http://learn.jquery.com/plugins/stateful-plugins-with-widget-factory/
In the future you can consider rewriting your plugin to use the widget factory. It would certainly make your code simpler ;)
Try this pattern
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
(function ($) {
var defaults = {
"text": "abcdefg",
}
, options = $.extend({}, defaults, options);
$.fn.plugin = function (options) {
var options = (function (opts, def) {
var _opts = {};
if (typeof opts[0] !== "object") {
_opts[opts[0]] = opts[1];
};
return opts.length === 0
? def
: typeof opts[0] === "object"
? opts[0] : _opts
}([].slice.call(arguments), defaults));
return $(this).text(options.text)
}
}(jQuery));
$(".results:eq(0)").plugin(); // return `defaults`
$(".results:eq(1)").plugin({"text":"gfedcba"}); // return `options`
$(".results:eq(2)").plugin("text", 123); // return `arguments` as `options`
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="results"></div><br />
<div class="results"></div><br />
<div class="results"></div>

jQuery off not working as I expected

Resolved!! see the end of the question for the result that I used
I am trying to write a function that can handle my apps paging by routes.
I have a function route() that is called with argument being the route(page) to move to.
route is an object that defines a model that it uses that handles its logic.
This model contains 3 functions
indexAction
- This renders my view and appends it to my page.
bindEvents
- This is where I have placed all of my click events
shutDown
- This is instructions to run when moving to a new page
The router function first runs shutdown on the current page, here I have the $(selector).off() and $(selector).remove()
it then runs the enidexAction and bindEvents function.
My issue now is when I return to this page, all my click functions are running twice, then three times etc... its as if the off() never actually unbind from the anchor.
here is an example of one of my models
var NewPageModel = (function() {
var instance;
var modal = 'null';
function createInstance() {
var object = {
indexAction: indexAction,
shutDown: shutDown,
bindEvents: bindEvents
};
return object;
}
function indexAction (data, callback){
var partials = {};
ViewManager.render('pageName',{context:data}, partials,function(html){
ViewManager.appendUnique('#xxx',html,'uniqueID');
callback();
});
}
/**
* Remove modal
*/
function shutDown(){
this.modal.off();
this.modal.remove();
}
function bindEvents() {
if(this.modal!='null'){
return;
}
this.modal = $(PagerManager.pages.newGroup.id);
this.modal.on('click','div.close', function () {
shutDown();
});
this.modal.on('click', 'button.cancel', function () {
shutDown();
});
this.modal.on('click', 'button.submit', function () {
//code that submits form information
});
}
return {
getInstance: function () {
if (!this.instance) {
this.instance = createInstance();
}
return this.instance;
}
};
})();
EDIT!!
So I am still learning about the importance of scopes and how they can be applied to functions
Here is the working code
var NewPageModel = (function() {
var instance;
var modal;
function createInstance() {
var object = {
indexAction: indexAction,
shutDown: shutDown,
bindEvents: bindEvents
};
return object;
}
function indexAction (data, callback){
var partials = {};
ViewManager.render('pageName',{context:data}, partials,function(html){
ViewManager.appendUnique('#xxx',html,'uniqueID');
callback();
});
}
/**
* Remove modal
*/
function shutDown(){
this.modal.off();
this.modal.remove();
this.modal = null;
}
function bindEvents() {
//This is confused logic, if I use off() in shutdown, I don't need to do this as I need to bind all the events again. hence in shutdown modal=null;
if(!this.modal){
return;
}
this.modal = $('#modal');
this.modal.on('click','div.close', function () {
shutDown().apply(this);
}).bind(this);;
this.modal.on('click', 'button.cancel', function () {
shutDown().apply(this);
}).bind(this);;
this.modal.on('click', 'button.submit', function () {
//here I only use the apply(this) if I use another internal function
//code that submits form information
}).bind(this);;
}
return {
getInstance: function () {
if (!this.instance) {
this.instance = createInstance();
}
return this.instance;
}
};
})();
You are losing your this in the event handler functions (this will be the element clicked) so the shutDown is not getting the correct this:
this.modal.on('click','div.close', function () {
shutDown();
});
should be:
var self = this;
this.modal.on('click', 'button.cancel', function () {
self.shutDown();
});
e.g.
function bindEvents() {
var self = this;
if(this.modal!='null'){ /// <<<< !!!!!! WTF
return;
}
this.modal = $(PagerManager.pages.newGroup.id);
this.modal.on('click','div.close', function () {
self.shutDown();
});
this.modal.on('click', 'button.cancel', function () {
self.shutDown();
});
this.modal.on('click', 'button.submit', function () {
//code that submits form information
});
}
Note: I am ignoring the string comparison to null for now as I have no clue what you are doing there :)
As pointed out in comment by #Gurami Dagundaridze you can also retain the correct this using bind (I think the syntax goes like this):
this.modal.on('click', 'button.cancel', shutDown.bind(this));
In the spirit of keeping your syntax and just fixing the bug,
if(this.modal!='null'){ should be if(modal!='null'){
Because this.modal will be undefined at that condition and will just return.
In the spirit of fixing your code, you need to keep a reference to this or it will default to window in the browser.
var modal;
function createInstance() {
var object = {
modal : modal,
shutDown: shutDown,
bindEvents: bindEvents
};
return object;
}
function bindEvents() {
if(this.modal){
return;
}
// ..... //
this.modal.on('click','div.close', function () {
shutDown.apply(this);
}.bind(this));
// ..... //
}
Working demo :
http://jsfiddle.net/uyovgdj3/

Defining computed observables on members of child array using Knockout mapping plugin

I have the following:
// Child Array is Cards, trying to add computed observable for each child
var CardViewModel = function (data) {
ko.mapping.fromJS(data, {}, this);
this.editing = ko.observable(false);
};
var mapping = {
'cards': { // This never gets hit, UNLESS I remove the 'create' method below
create: function (options) {
debugger;
return new CardViewModel(options.data);
}
},
create: function(options) {
var innerModel = ko.mapping.fromJS(options.data);
innerModel.cardCount = ko.computed(function () {
return innerModel.cards().length;
});
return innerModel;
}
};
var SetViewModel = ko.mapping.fromJS(setData, mapping);
debugger;
ko.applyBindings(SetViewModel);
However I can't get the 'cards' binding to work - that code isn't reached unless I remove the 'create' method. I'm trying to follow the example from the knockout site:
http://knockoutjs.com/documentation/plugins-mapping.html
They do this for the child object definition:
var mapping = {
'children': {
create: function(options) {
return new myChildModel(options.data);
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
With the ChildModel defined like this:
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this);
this.nameLength = ko.computed(function() {
return this.name().length;
}, this);
}
I've spent the past day on this and cannot for the life of me figure out why this isn't working. Any tips would be awesome.
EDIT: Here's a fiddle of what I'm working with. It's only showing SIDE 1 in the result because "editing" isn't recognized here:
<div data-bind="visible: !$parent.editing()" class="span5 side-study-box">
http://jsfiddle.net/PTSkR/1/
This is the error I get in chrome when I run it:
Uncaught Error: Unable to parse bindings. Message: TypeError: Object
has no method 'editing'; Bindings value: visible: !$parent.editing()
You have overridden the create behavior for your view model. The mapping plugin will not call any of the other handlers for the properties for you. Since you're mapping from within the create method, move your cards handler in there.
var mapping = {
create: function(options) {
var innerModel = ko.mapping.fromJS(options.data, {
'cards': {
create: function (options) {
debugger;
return new CardViewModel(options.data);
}
}
});
innerModel.cardCount = ko.computed(function () {
return innerModel.cards().length;
});
return innerModel;
}
};
updated fiddle
you didnt needed to have parenthesis. I just changed from
!$parent.editing()
to
!$parent.editing
See the updated fiddle here

Categories