jQuery - proper way to create plugin - javascript

I'm trying to convert some of my code to reusable plugins.
Many times I'm filling selects with dynamic options that comes from Ajax request.
I've managed to create something like this:
$.fn.fillSelect = function fillSelect(options) {
var self = this;
options = $.extend({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/StatusList",
dataType: "json",
async: true,
success: function(data) {
var list = "";
$.each(data.d, function(i) {
list += '<option value='
+ data.d[i].ID + '>'
+ data.d[i].Nazwa
+ '</option>';
});
self.filter("select").each(function() {
$(this).empty();
$(this).append(list);
//use selectmenu
if ($.ui.selectmenu) $(this).selectmenu();
});
}//,
//error: function(result) {
// alert("Error loading data!");
//}
}, options);
$.ajax(options);
return self;
}
Idea behind this is to be able to fill multiple selects with the same data multiple times with one request.
I have default options for Ajax request, but I would like to add some more options to it.
For example:
clear - fill determinate if I want new options to replace existing ones or append.
Also I would like to add some callbacks to my function that I could pass as parameters.
If for example server request will fail I would like to specify a function that will be called after this error occurs - for example to show alert or disable my selects.
My question is how should I change my plugin or which pattern (boilerplate) I should use?
Every boilerplate I found is for creating plugins that will 'stay' inside selected item, so that it is possible to call method of that plugin later.
I need a simple plugin that will allow user to fill select and then it will end it's life :)
My main idea is to do only one request to server for all elements.
Here is jsfiddle demo: http://jsfiddle.net/JC7vX/2/

A basic plugin can be built as follows
(function ($){
$.fn.yourPlugin = function (options){
// this ensures that function chaining can continue
return this.each(function (){
// merge defaults and user defined options
var params = $.extend({},defaultOptions,options);
// your plugin code
});
}
/* these options will help define the standard functionality of the plugin,
* and also serves as a nice reference
*/
var defaultOptions = {
someProperty : true
}
})(jQuery)
There are other things that you can do to extend the functionality of your plugin and give public methods that retain the context, but that would be overkill for your example.

This is my version of answer http://jsfiddle.net/Misiu/ncWEw/
My plugin looks like this:
(function($) {
$.fn.ajaxSelect = function(options) {
var $this = this;
//options
var settings = $.extend({}, defaults, options);
//disable select
if ($.ui.selectmenu && settings.selectmenu && settings.disableOnLoad) {
$this.selectmenu('disable');
}
//ajax call
$.ajax({
type: settings.type,
contentType: settings.contentType,
url: settings.url,
dataType: settings.dataType,
data: settings.data
}).done(function(data) {
var n = data.d || data;
var list = "";
$.each(n, function(i) {
list += '<option value=' + n[i].Id + '>' + n[i].Nazwa + '</option>';
});
$this.filter("select").each(function() {
$(this).empty();
$(this).append(list);
if ($.ui.selectmenu && settings.selectmenu) {
$this.selectmenu();
}
settings.success.call(this);
});
}).fail(function() {
settings.error.call(this);
});
return this;
};
var defaults = {
type: "POST",
contentType: "application/json; charset=utf-8",
url: '/echo/json/',
dataType: 'json',
data: null,
async: true,
selectmenu: true,
disableOnLoad: true,
success: function() {},
error: function() {}
};
})(jQuery);
I understand that it is very simple, but it has all functionality that I needed:
-You can select multiple elements at one time
-It filters only selects from Your selected items
-It makes only one request to server
-First it builds option string and then append it instead of adding items in loop
-You can specify 2 callbacks: one for error and second for success
And it is my first plugin, so there is much places for improvements.
As always comments and hints are welcome!

Related

Editable resolves to undefined mixing KnockOutJS and plain Javascript

I tried to create a drop-down menu using options binding in KnockOut JS (ko.plus to be precise). Things were running as expected until I mixed my solution up with this jsfiddle: http://jsfiddle.net/jnuc6y05/ in order to place a default option in the list. The problem lies in "HERE" (please see the code) where I get
error message
"TypeError: this.fieldStreetApallou is not a function"
As I said I had no problem, and I think mixing plain javascript with KO caused the situation. I tried to unwrap the editable with no luck since it resolves to undefined. Even ko.toJS does not do the trick (undefined again).
I don't have any serious experience with KO and furthermore with Javascript, and any help would be greatly appreciated.
PS: Reduced code provided
/////// HTML
<input data-bind="value: fieldStreetApallou, enable: fieldStreetApallou.isEditing" />
Rename
<div data-bind="visible: fieldStreetApallou.isEditing">
Confirm
Cancel
</div>
/////// Javascript
<script type="text/javascript">
ko.observableArray.fn.find = function(prop, data) {
var valueToMatch = data[prop];
return ko.utils.arrayFirst(this(), function(item) {
return item[prop] === valueToMatch;
});
};
var availableCompanies = [{
offset: 1,
name: "Company1"
}, {
offset: 2,
name: "Company2"
}
// ...more pairs here
];
//Default pairs for the drop-down menus
var selectedCompanyApallou = {
offset: 1,
name: "Company1"
};
var ViewModel = function(availableCompanies, selectedCompanyApallou) {
this.availableCompaniesApallou = ko.observableArray(availableCompanies);
this.selectedCompanyApallou = ko.observable(this.availableCompaniesApallou.find("offset", selectedCompanyApallou));
this.fieldStreetApallou = ko.editable("Initial value");
postStreetFieldToServerForApallou = function() {
$.ajax({
type: "PUT",
url: "http://www.san-soft.com/goandwin/addresses/" + 15,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "Address_id=15&Street=" + this.fieldStreetApallou() //<---- HERE!
}).done(function(data) {
alert("Record Updated Successfully " + data.status);
}).fail(function(err) {
alert("Error Occured, Please Reload the Page and Try Again " + err.status);
});
};
};
ko.applyBindings(new ViewModel(availableCompanies, selectedCompanyApallou));
</script>
I think you linked to the wrong JSFiddle.
Looks like this is not what you are expecting when postStreetFieldToServerForApallou is called by the button click. this in JavaScript is based on who called the function.
To work around it in this case, I like to set var self = this; at the top of the view model so self always points to the view model, then I replace all instances of this with self. This is really only needed on your HERE line, but it simplifies to use self throughout.
The fixed view model code:
var ViewModel = function(availableCompanies, selectedCompanyApallou) {
var self = this;
self.availableCompaniesApallou = ko.observableArray(availableCompanies);
self.selectedCompanyApallou = ko.observable(self.availableCompaniesApallou.find("offset", selectedCompanyApallou));
self.fieldStreetApallou = ko.editable("Initial value");
postStreetFieldToServerForApallou = function() {
$.ajax({
type: "PUT",
url: "http://www.san-soft.com/goandwin/addresses/" + 15,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
data: "Address_id=15&Street=" + self.fieldStreetApallou() //<---- HERE!
}).done(function(data) {
alert("Record Updated Successfully " + data.status);
}).fail(function(err) {
alert("Error Occured, Please Reload the Page and Try Again " + err.status);
});
};
};

Synchronize Ajax Calls and executeQueryAsync SharePoint JS CSOM

I have a problem synchronizing calls using Rest Api and JavaScript Object Model.
I'm currently working with Client Side Rendering to customize a view for a Document Library and add some functionalities in this custom UI.
I have a small collection of id's, and I'm looping through this collection and make some ajax calls with each of this items.
The results of this operation is to perform some tasks and to update my UI when all these operations are completed to refresh my UI and display some icons.
What I expect is to have 3 icons displayed only for my three first items.
The problem is that sometimes it displays all the icons, sometimes the two first... randomly.
I know that there is some problems with the synchronization of my executeQueryAsync calls, I've learned about jQuery Deferred object, I've tried to use them but without results.
Below you'll find screenshots of what I expect.
Expected :
https://onedrive.live.com/redir?resid=E2C3CC814469DA54!3070&authkey=!AEf_C0XGDwfuFRY&v=3&ithint=photo%2cpng
What would be the good way of using deferred ? Could anyone help ?
Thanks a lot
Elhmido
This is my main function for overriding the display :
(function () {
var accordionContext = {};
accordionContext.Templates = {};
// Be careful when add the header for the template, because it's will break the default list view render
accordionContext.Templates.Item = itemTemplate;
// Add OnPostRender event handler to add accordion click events and style
accordionContext.OnPreRender = [];
accordionContext.OnPreRender.push(function () {
$(function () {
IsCurrentUserMemberOfGroup("TEST Owners");
**$.when(IsUserApprover(arrayOfIDS).done(function () {
displayIcons();
}));**
});
});
accordionContext.OnPostRender = [];
accordionContext.OnPostRender.push(function () {
$(function () {
accordionOnPostRender();
fixColumns();
audit.relativeUrl = _spPageContextInfo.webAbsoluteUrl;
});
});
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionContext);
})();
The function where I have the problem,
function IsUserApprover(auditTab) {
var dfd = $.Deferred();
audit.tabIcons = new Array();
for (var i = 0; i < auditTab.length; i++) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('Audit')/items?$select=UserID&$filter=ID eq " + auditTab[i] + "";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
var userId = data.d.results[0].UserID;
var context = SP.ClientContext.get_current();
var auditor = context.get_web().ensureUser(userId);
context.load(auditor);
//I think the problem is here because I don't know how to handle this call
context.executeQueryAsync(userLoaded, userFailed);
function userLoaded() {
var auditorId = auditor.get_id();
checkAuditorValidator(auditorId);
dfd.resolve();
}
function userFailed(sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}
});
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
dfd.reject();
});
}
return dfd.promise();
}
function checkAuditorValidator(auditorId) {
var uri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/Lists/GetByTitle('SecurityMgmt')/items?" +
"$select=Auditeur/ID,Validateur/ID" +
"&$expand=Auditeur/ID,Validateur/ID" +
"&$filter=(Auditeur/ID eq '" + auditorId + "') and (Validateur/ID eq '" + _spPageContextInfo.userId + "')";
var call = $.ajax({
url: uri,
type: "GET",
dataType: "JSON",
async: false,
headers: {
"Accept": "application/json;odata=verbose"
}
});
call.done(function (data, status, jqxhr) {
if (data.d.results.length > 0) {
if (audit.UserAdmin) {
audit.tabIcons.push(true);
}
}
else {
audit.tabIcons.push(false);
}
});
call.fail(function (jqxhr, status, error) {
alert(JSON.stringify(error))
});
}
Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), synchronous requests on the main thread have been deprecated due to the negative effects to the user experience.
You should avoid synchronous ajax calls...
I had the same problem and solved by adding an id during the custom rendering of the fields (items), on the postrender call my service asynchronously and according the result edit the OnPreRender page using the previously added ids.
I also did some hacks...e.g overriding the standard function RenderItemTemplate. Yes I know, it's not very clean but it works like a charm.

replacing select box dynamically inside of a div

I want replace the select box inside of the div
<div class="models">
<select disabled="disable">
<option>Model Name</option>
</select>
</div>
I'm trying to target the div and load the select box like this
jQuery('.models select').change(function() {
var model = jQuery('.models option:selected').text();
I'm not getting any action on change though
http://jsfiddle.net/HNgKt/
Simple change: bind your change event handler to the container div (which should be present when this executes) and get the text value from that:
jQuery('.models').on('change','select',function() {
var model = jQuery(':selected',this).text();
var modelValue = jQuery(':selected',this).val();
});
Note: your fiddle and markup has it diabled, of course it would need to be enabled first, something like:
jQuery('.models>select').prop('disabled',false);
EDIT: Using your fiddle, I mashed around, commented out your load - as it does not work there and the cleanstring, not present, and this works:
jQuery('.brands').change(function () {
alert('here');
var brand = jQuery('.brands option:selected').text();
// brand = cleanString(brand);
//jQuery('.models').load('/pf-models #' + brand);
jQuery('.models>select').append('<option >She is a classic</option>').prop('disabled', false);
});
alert(jQuery('.models>select').prop('disabled'));
jQuery('.models').on('change', 'select', function () {
var model = jQuery(":selected", this).text();
alert(model);
model = cleanString(model);
jQuery('#show-models').load('/pf-urls #' + model);
});
updated fiddle: http://jsfiddle.net/HNgKt/6/
EDIT Further detailed example, still based on the valid markup assumptions coming back from the load on the first part which I have substituted for a html replace since we have not access to that part:
jQuery('.brands').change(function () {
var brand = jQuery('.brands option:selected').text();
$('.models').html('<select class="models"><option >' + brand + ' She is a classic</option><option>clunker</option></select>');
});
jQuery('.models').on('change', 'select', function () {
var model = jQuery(":selected", this).text();
alert('model:' + model);
});
Fiddle for that: http://jsfiddle.net/HNgKt/7/
Alerts the model if you choose a brand, then a model.
Try following steps,
on change of brands list make an ajax call and make sure in result you recieve the new list options or you can dynamically prepare the options list in jquery also.
And on success of call repopulate new list with the received data.
jQuery('.brands').change(function() {
var brand = jQuery('.brands option:selected').text();
brand = JSON.stringify(cleanString(brand));
$.ajax({
type: "GET", //GET or POST or PUT or DELETE verb
url: ajaxUrl, // Location of the service
data: brand , //Data sent to server
contentType: "", // content type sent to server
dataType: "json", //Expected data format from server
processdata: true, //True or False
success: function (data) {//On Successful service call
var $newList = $(".models select'").empty();
$newList.append(data);
},
error: function(){} // When Service call fails
});
});
Try the following:
/* using delegate version of .on */
jQuery(document).on('change', '.brands', function() {
var brand = jQuery('.brands option:selected').text();
brand = cleanString(brand);
jQuery('.models').load('/pf-models #' + brand);
});
jQuery(document).on('change', '.models select', function() {
var model = jQuery('.models option:selected').text();
model = cleanString(model);
jQuery('#show-models').load('/pf-urls #' + model);
});
For dealing with "dynamic" elements, you want to use delegate to assign action. This basically reserves a method to be assigned to all elements who match the description.
See also:
http://api.jquery.com/delegate/
http://api.jquery.com/on/#direct-and-delegated-events

Convert function into plugin

I have a function that I call multiple times in my projects:
function fillSelect(select) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/Status",
dataType: "json",
async: true,
success: function(data) {
$.each(data.d, function(i) {
select.append('<option value=' + data.d[i].value + '>' + data.d[i].name + '</option>');
});
},
error: function(result) {
alert("Error occured. Contact admin");
}
});
}
Then in my code I'm using this like so:
fillSelect($('select#status1'));
fillSelect($('select#status2'));
fillSelect($('select#status3'));
What I would like to do is to convert my function into plugin, so I would be able to call it as so:
$('select#status1, select#status2, select#status3').fillSelect();
Using http://starter.pixelgraphics.us/ I've generated empty schema:
(function($) {
$.ajaxSelect = function(el, select, options) {
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("ajaxSelect", base);
base.init = function() {
base.select = select;
base.options = $.extend({}, $.ajaxSelect.defaultOptions, options);
// Put your initialization code here
};
// Sample Function, Uncomment to use
// base.functionName = function(paramaters){
//
// };
// Run initializer
base.init();
};
$.ajaxSelect.defaultOptions = {
clear: false //append to select or replace current items
};
$.fn.ajaxSelect = function(select, options) {
return this.each(function() {
(new $.ajaxSelect(this, select, options));
});
};
})(jQuery);
but I don't know how to fill it.
What I would like to do is to call sever ones and then fill as many select items as I put in parameters.
Is all that code really necessary for such a small plugin?
I know that there are probably some plugins that this functionality, but I would like to create my own plugin, just to learn a bit more :)
You don't need all that boiler plate you could do as below
$.fn.fill = function fillSelect(options) {
var self = this;
options = $.extend({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "Data.asmx/Status",
dataType: "json",
async: true,
success: function(data) {
var list = "";
$.each(data.d, function(i) {
list += '<option value='
+ data.d[i].value + '>'
+ data.d[i].name
+ '</option>';
});
self.filter("select").each(function(){
$(this).append(list);
});
},
error: function(result) {
alert("Error occured. Contact admin");
}
},options);
$.ajax(options);
return this;
}
the first thing to notice that the function is added to the jQuery prototype/$.fn. Then the success handler have been changed so that all selected elements will be handled and lastly the selection is returned to make chaining possible, as this is usually expect when using jQuery.
The above code will append the same options to all selected "select" elements only. If you select something else the options will not be appended to those elements.
I've changed the signature to accept an options element. In the above version there's default vesrion equaling your ajax options. If other values are supplied, they will override the default ones if a default exist. If a default does not exist the values will be added to the options object
You just need to add your method to the $.fn object, as described here: http://docs.jquery.com/Plugins/Authoring
The this keyword will evaluate to the jQuery selector that was used to invoke your function's code, so instead of using the select parameter in your code, just use this

Advice requested - passing variables between functions using json/jquery & ajax

I've looked over a lot of 'similar' q&a threads on SO but to be honest, as I don't have too much of a grip on js programming, I'm finding it difficult to make sense of a lot of the answers (as far as they may apply to my own situation).
The context is this, I have two php scripts one returning a list of customer_ids (json encoded) for a set period and the other returning their preferences for news feeds (json encoded).
I wrote the following, having googled a bit to get a basic understanding of how to setup an ajax function in jQuery:
$('document').ready(function() {
$.ajax({
type:'GET', url: 'cust_selection.php', data: '',
succes:function(cstmrid) {
var clistlen = cstmrid.length;
var i=0;
var cstmr;
for( ;cstmr=cstmrid[i++]; ) {
$('#adminPanel>ul>li').append("<a href='' onclick='alert("+cstmr+")' class='lst_admin basic'>"+cstmr+"</a>"); //alert to be replaced with a function call which passes customerid to the function below.
}
},
dataType:'json'
});
var cstmrid = "483972258"; //hardcoded for testing purposes
$.ajax({
type:'GET', url:'newsfpref.php?', data:'cref='+cstmrid,
success:function(npfdata) {
var item;
var n=0;
for( ;item=npfdata[n++]; ) {
var news = npfdata[n].nsource;
$('#adminMain>table>tbody').append("<tr><td>"+item+"</td></tr>");
}
},
dataType:'json'
});
});
Now from the first ajax function, I get a list of links which I want to be able to click to launch the second ajax function and pass it the customer id so that it can grab a list of the news sources that they've configured for their pages.
The alert and the hard-coded customer id both suggest that the functions are 'working', but when I try and adjust the first function so that:
...
$('#adminPanel>ul>li').append("<a href='' onclick='getCustomerNP("+cstmr+")' class='lst_admin basic'>"+cstmr+"</a>");
... is calling a modified version of the second function, as below:
...
function getCustomerNP(cstmrid) {
$.ajax({
type:'GET', url:'newsfpref.php?', data:'cref='+cstmrid,
success:function(nprfdata) {
var item;
var n=0;
for( ;item=npfdata[n++]; ) {
var news = npfdata[n].nsource;
$('#adminMain>table>tbody').append("<tr><td>"+item+"</td></tr>");
}
},
dataType:'json'
});
}
Everything seems to just fail at this point. The second function doesn't seem to 'receive' the variable and I'm not sure if it's something elementary that I've overlooked (like some muddled up " and ' placements) or if what I am trying to accomplish is actually not the way jQuery ajax functions interact with each other.
As you can see, I've cannibalised bits of code and ideas from many SO q&a threads, but copying without much of an understanding makes for a frustratingly dependent life.
I would appreciate as much - expansive - comment as you can provide, as well as a solution or two (naturally).
EDIT: Not to confuse anyone further, I've been modifying the above and correcting my (many) errors and typos along the way. At present, the code looks like below:
$('document').ready(function () {
$.ajax({
type: 'GET', url: 'cust_selection.php', data: '',
succes: function (cstmrid) {
var clistlen = cstmrid.length;
var i = 0;
var cstmr;
for (; cstmr = cstmrid[i++]; ) {
var a = $("<a href='' class='lst_admin basic'>" + cstmr + "</a>").click(function () {
getCustomerNP(cstmr)
})
$('#adminPanel>ul>li').append(a); //alert to be replaced with a function call which passes customerid to the function below.
}
},
dataType: 'json'
});
function getCustomerNP(cstmr) {
alert(cstmr);
}
});
You've got a typo in the $.ajax() success function within getCustomerNP(). The function declaration:
success:function(nprfdata) {
... has a parameter nprfdata, but then within the function you use npfdata (missing the r).
Also this code:
var item;
var n=0;
for( ;item=npfdata[n++]; ) {
var news = npfdata[n].nsource;
$('#adminMain>table>tbody').append("<tr><td>"+item+"</td></tr>");
}
...declares and sets variable news that you never use. And it doesn't seem right to increment n in the for test expression but then use n within the loop. You never set item to anything but you use it in your .append().
(Note also that JS doesn't have block scope, only function scope, so declaring variables inside an if or for loop doesn't limit them to that if or for block.)
I would not create inline onclick handlers like that. I'd probably do something more like this:
$('#adminPanel>ul>li').append("<a href='' data-cstmr='"+cstmr+"' class='lst_admin basic'>"+cstmr+"</a>");
...and then within the document ready setup a delegated event handler to catch the clicks on those anchors:
$('#adminPanel>ul>li').on('click', 'a.lst_admin', function() {
$.ajax({
type:'GET', url:'newsfpref.php?', data:'cref='+ $(this).attr('data-cstmr'),
success:function(npfdata) {
var item,
n=0,
// cache the jQuery object rather than reselecting on every iteration
$table = $('#adminMain>table>tbody');
// increment n only after the current iteration of the loop
for( ;item=npfdata[n]; n++) {
// change to use item
$table.append("<tr><td>"+item.nsource+"</td></tr>");
}
},
dataType:'json'
});
});
As you append your like with <a href='' onclick='getCustomerNP("+cstmr+")', Make sure you can access the function getCustomerNP.
Try to define getCustomerNP as
window.getCustomerNP = function(cstmrid) {
...
If you defined it in the $(document).ready(function(){ ... }) block, try this
$('document').ready(function () {
$.ajax({
type: 'GET', url: 'cust_selection.php', data: '',
succes: function (cstmrid) {
var clistlen = cstmrid.length;
var i = 0;
var cstmr;
for (; cstmr = cstmrid[i++]; ) {
var a = $("<a href='' class='lst_admin basic'>" + cstmr + "</a>").click(function () {
getCustomerNP(cstmr)
})
$('#adminPanel>ul>li').append(a); //alert to be replaced with a function call which passes customerid to the function below.
}
},
dataType: 'json'
});
function getCustomerNP(cstmrid) {
$.ajax({
type: 'GET', url: 'newsfpref.php?', data: 'cref=' + cstmrid,
success: function (nprfdata) {
var item;
var n = 0;
for (; item = npfdata[n++]; ) {
var news = npfdata[n].nsource;
$('#adminMain>table>tbody').append("<tr><td>" + item + "</td></tr>");
}
},
dataType: 'json'
});
}
});

Categories

Resources