Javascript not recognizing self.function when inside onclick event - javascript

I have a Javascript file that I added to. It's for a twitter plugin, and I'm adding a filter function.
This is my script (the relevant parts):
;(function ( $, window, document, undefined ) {
var pluginName = "twitterFeed",
defaults = {
username: null,
webservice_url: "/services/Scope/Country_United_States_Internet/TwitterService.svc/DoTwitterSearch",
num_tweets: 3
};
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.options = $.extend( {}, defaults, options );
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
//if username is unknown
if(this.options.username == null) {
// do nothing
try{console.log('twitter username not found')}catch(err){};
return false;
}
// Get the tweets to display
this.getTweets();
$(".twitter-search input").on("change", function () {
var filters = this.formatFilters($(this).val());
this.getTweets(filters);
});
},
formatFilters : function(filterString) {
var hashtags = filterString.split(" ");
var hashtagString = "";
for (var i = 0; i < hashtags.length; i++) {
var hashtag = hashtags[i];
hashtag = hashtag.replace(",", "");
if (hashtag[0] !== "#") {
hashtag = "#" + hashtag;
}
hashtagString += " " + hashtag;
}
return hashtagString;
},
getTweets : function(filters){
var self = this;
var query = "from:" + self.options.username;
if (filters) {
query += filters;
}
var post_data = JSON.stringify(
{
"PageSize" : self.options.num_tweets,
"TwitterQuery" : query
}
);
$.ajax({
type: "POST", // Change to POST for development environment
url: this.options.webservice_url,
data: post_data,
contentType: "application/json; charset=utf-8",
dataType: "json",
timeout:2000,
success: function(data) {
// render the tweets
self.renderTweets(data.ContentItems);
},
error: function(error, type){
try{console.log(type);}catch(err){}
}
});
},
I added the $(".twitter-search input") on change event (in init) and I added the formatFilters() function. However, in the onchange function, I get the error "this.formatFilters() is not defined". I tried removed this but still got "formatFilters() is not defined.

Remember that this inside of an event handler means whatever HTML element the event was activated on.
Instead, you need to keep track of the actual Plugin object, not the HTML element.
var self = this;
$(".twitter-search input").on("change", function () {
var filters = self.formatFilters($(this).val());
self.getTweets(filters);
});

The problem you are experiencing is with function scope. When you refer to this in the event handler it points to the callback scope and not the scope of your formatFilters function.
To fix it - In the init function add var self = this; on the first line and then change the call to use self.formatFilters instead of this.formatFilters

Related

An efficient way to call type specific functions

this is my situation:
I have a Field.js file which contains a bunch of classes (made with this plugin) each corresponding to a datatype on the page.
An example of a class:
$.Class("Types_UserId_Js",{
init_done : false,
validate : function (value){
return true;
}
},{
container : "",
UserIdDisplay : function (){
var associations = [];
var uid = 0;
$( container ).find(".userid_display").each(function(index,el){
uid = $( this ).find(".userid_value").val();
url = siteurl + "/dname?";
$.ajax({
type: "POST",
url: url,
data: ajaxData,
dataType: "json",
success: function(result, status){
associations[uid] = result;
}
});
});
},
init : function ( container ) {
if(container.length > 0 && !Types_UserId_Js.init_done){
this.container = container;
this.UserIdDisplay();
Types_UserId_Js.init_done = true;
}
};
});
(It's a dummy class for now).
I also have some html code that renders the types UI, in a standard format.
In the end, I have a page with a bunch of different types of inputs, and they all need their specific init function to be called in order to render properly.
What I did up to now is simply invoke EVERY init function in the Field.js file, like so:
$( document ).ready(function(ev){
var cont = $("#container");
var uid = new Types_UserId_Js(cont);
var text = new Types_Text_Js(cont);
// And so forth
});
I'd really like to know if there is a more efficient way to call every init function in the file without having to call them individually.
The Field.js is part of the main framework, and it is maintained by a third party developer so, while I can and do edit it to my leisure, I'd prefer to keep the generic structure that they imposed.
Thank you,
I think you'd need some mapping field <-> function. You can add data-attributes to the fields with the name of the fields init function. Then you can just loop over your fields, get the value and execute the function.
Check the following snippet:
// helper function borrowed from http://stackoverflow.com/a/12380392/4410144
var getFunctionFromString = function(string) {
var scope = window;
var scopeSplit = string.split('.');
for (i = 0; i < scopeSplit.length - 1; i++) {
scope = scope[scopeSplit[i]];
if (scope == undefined) return;
}
return scope[scopeSplit[scopeSplit.length - 1]];
}
var myFunction = function() {
console.log('myFunction');
}
var myOtherFunction = function() {
console.log('myOtherFunction');
}
$('input').each(function() {
var initFunction = getFunctionFromString($(this).data('function'));
initFunction();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input data-function="myFunction" />
<input data-function="myOtherFunction" />

How to make remote jquery validation for tied fields?

Let's say I have couple of input fields - their combination must be unique.
Each of them causes remote validation method triggering - and it's the same method for both fields. If combination is unique - it returns true.
The problem is following: when after validation error I change the field, that is not marked as erroneous, the erroneous field keeps being considered erroneous, even if method returns true (the couple is unique)!
I even don't need to make extra request to server, because the couple is unique! I just need to clear error for field, marked erroneous. However, I have not managed to do this - seems like jquery does not offer functionality for this.
Any ideas?
The relevant code is pretty huge, but the key parts are here:
this.clearErrors = function ($elements) {
var $validator = $elements.first().closest('form').validate();
$elements.each(function(index, item) {
var $parent = $(item).parent();
var element = $(item).get(0);
if ($parent.is('td')) {
$parent.removeClass(window.resources.errorCellClass);
}
$parent.find('span.' + window.resources.errorSpanClass).remove();
$validator.successList.push(element);
delete $validator.invalid[element.name];
delete $validator.submitted[element.name];
});
};
//Fixing remote method, since original one returned "pending" status all the time, as reported in other stackoverflow question
$.validator.addMethod('synchronousRemote', function (value, element, param) {
if (this.optional(element)) {
return 'dependency-mismatch';
}
var previous = this.previousValue(element);
if (!this.settings.messages[element.name]) {
this.settings.messages[element.name] = {};
}
previous.originalMessage = this.settings.messages[element.name].remote;
this.settings.messages[element.name].remote = previous.message;
if (typeof param == 'string') {
param = { url: param }
}
if (previous.old === value) {
return previous.valid;
}
previous.old = value;
var validator = this;
this.startRequest(element);
var data = {};
data[element.name] = value;
var valid = 'pending';
$.ajax($.extend(true, {
url: param,
async: false,
mode: 'abort',
port: 'validate' + element.name,
dataType: 'json',
data: data,
success: function (response) {
validator.settings.messages[element.name].remote = previous.originalMessage;
valid = response === true || response === 'true';
if (valid) {
var submitted = validator.formSubmitted;
validator.prepareElement(element);
validator.formSubmitted = submitted;
validator.successList.push(element);
delete validator.invalid[element.name];
validator.showErrors();
} else {
var errors = {};
var message = response || validator.defaultMessage(element, 'remote');
errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
validator.invalid[element.name] = true;
validator.showErrors(errors);
}
previous.valid = valid;
validator.stopRequest(element, valid);
}
}, param));
return valid;
});
$root.filter(':input[data-excluded-values-method]:not([readonly])').add($root.find(':input[data-excluded-values-method]:not([readonly])')).each(function () {
var $element = $(this);
$element.validate({
onkeyup: false
})
var $entityContainer = $element.closest('[data-entity]');
var $keyFields = $entityContainer.filter('INPUT[data-is-key]:not([disabled])').add($entityContainer.find('INPUT[data-is-key]:not([disabled])'));
var localizedNames = [];
$keyFields.each(function () {
localizedNames.push($(this).attr('localized-name'));
});
$element.rules('add',
{
synchronousRemote: function () {
var key = [];
var keyIsUnique = true;
$keyFields.each(function () {
key.push($(this).val());
});
return {
url: $element.attr('data-excluded-values-method'),
type: 'POST',
async: false,
data: JSON.stringify({
key: key,
entityType: $entityContainer.attr('data-entity')
}),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
dataFilter: function (isUnique) {
keyIsUnique = isUnique;
return isUnique;
},
complete: function () {
if (keyIsUnique === 'true') {
window.commonUtils.clearErrors($keyFields.filter('[name!="' + $element.attr('name') + '"]:input[data-excluded-values-method]:not([readonly])'));
}
}
}
},
messages: {
synchronousRemote: $.validator.format(window.resources.notUniqueValidationError)(localizedNames.join(' + '))
}
});
});
I've debugged jquery validate method and found what yet should be set to clear validation error:
$validator.previousValue(element).valid = true;
Now everything works.

How to extend object prototype created with anonymous function

I'm using "scrollPagination" javascript for ajax pagination.
here is a code:
(function( $ ){
$.fn.scrollFeedPagination = function(options) {
var opts = $.extend($.fn.scrollFeedPagination.defaults, options);
var target = opts.scrollTarget;
if (target == null){
target = obj;
}
opts.scrollTarget = target;
return this.each(function() {
$.fn.scrollFeedPagination.init($(this), opts);
});
};
$.fn.stopScrollPagination = function(){
return this.each(function() {
$(this).attr('scrollPagination', 'disabled');
});
};
$.fn.scrollFeedPagination.loadContent = function(obj, opts){
console.log(opts);
var target = opts.scrollTarget;
var mayLoadContent = ($(target)[0].scrollHeight - $(target).scrollTop()) == $(target).height();
if (mayLoadContent){
if (opts.beforeLoad != null){
opts.beforeLoad();
}
$(obj).children().attr('rel', 'loaded');
$.ajax({
type: 'POST',
url: opts.contentPage,
data: opts.contentData,
success: function(data){
console.log(data);
console.log(1);
var selector = target.selector;
var response = $(data).find(selector).children();
$(obj).append(response);
var objectsRendered = $(obj).children('[rel!=loaded]');
if (opts.afterLoad != null){
opts.afterLoad(objectsRendered);
}
},
dataType: 'html'
});
}
};
$.fn.scrollFeedPagination.init = function(obj, opts){
var target = opts.scrollTarget;
$(obj).attr('scrollPagination', 'enabled');
$(target).scroll(function(event){
if ($(obj).attr('scrollPagination') == 'enabled'){
$.fn.scrollFeedPagination.loadContent(obj, opts);
}
else {
event.stopPropagation();
}
});
$.fn.scrollFeedPagination.loadContent(obj, opts);
};
$.fn.scrollFeedPagination.defaults = {
'contentPage' : null,
'contentData' : {},
'beforeLoad': null,
'afterLoad': null ,
'scrollTarget': null,
'heightOffset': 0
};
})( jQuery );
it work fine when I itialize it on one node.
but two nodes interfere with each other:
example:
node1.pagination({
'contentPage': ajaxurl,
'contentData': {
action: 'chat_pagination',
'last_time' : last_time,
},
'scrollTarget': node1,
'beforeLoad': function() {},
'afterLoad': function(elementsLoaded) {
last_time = node1.children('div').last().data('last-id');
}
});
it works but as soon as I initialize another one
node2.pagination({
'contentPage': ajaxurl,
'contentData': {
action: 'feeds_pagination',
'last_time' : last_time,
},
'scrollTarget': node2,
'beforeLoad': function() {},
'afterLoad': function(elementsLoaded) {
last_time = node2.children('div').last().data('last-id');
}
});
now node1 updates with last_time of node2 and ajax action also 'feeds_pagination'.
Now to solve this, I need to have two instances of scrollFeedPagination();
is it possible extend with syntax like this?
var opts = $.extend($.fn.scrollFeedPagination.defaults, options);
Notice that the first parameter to $.extend is the object that is getting extended (with properties from the objects in the rest of the arguments), and is also the result of the function that is returned. So you're basically setting
var opts = $.fn.scrollFeedPagination.defaults
// extend it then:
$.extend(opts, options);
As you can see, both your opts objects will be the same thing actually, and extending it the second time will overwrite the first configuration. Instead, start with a new empty object, and extend it first with the defaults and then the options parameter:
var opts = $.extend({}, $.fn.scrollFeedPagination.defaults, options);

Class method in javascript is not a function

The answer must be obvious but I don't see it
here is my javascript class :
var Authentification = function() {
this.jeton = "",
this.componentAvailable = false,
Authentification.ACCESS_MASTER = "http://localhost:1923",
isComponentAvailable = function() {
var alea = 100000*(Math.random());
$.ajax({
url: Authentification.ACCESS_MASTER + "/testcomposant?" + alea,
type: "POST",
success: function(data) {
echo(data);
},
error: function(message, status, errorThrown) {
alert(status);
alert(errorThrown);
}
});
return true;
};
};
then I instanciate
var auth = new Authentification();
alert(Authentification.ACCESS_MASTER);
alert(auth.componentAvailable);
alert(auth.isComponentAvailable());
I can reach everything but the last method, it says in firebug :
auth.isComponentAvailable is not a function
.. but it is..
isComponentAvailable isn't attached to (ie is not a property of) your object, it is just enclosed by your function; which makes it private.
You could prefix it with this to make it pulbic
this.isComponentAvailable = function() {
isComponentAvailable is actually attached to the window object.
isComponentAvailable is a private function. You need to make it public by adding it to this like so:
var Authentification = function() {
this.jeton = "",
this.componentAvailable = false,
Authentification.ACCESS_MASTER = "http://localhost:1923";
this.isComponentAvailable = function() {
...
};
};
Another way to look at it is:
var Authentification = function() {
// class data
// ...
};
Authentification.prototype = { // json object containing methods
isComponentAvailable: function(){
// returns a value
}
};
var auth = new Authentification();
alert(auth.isComponentAvailable());

JavaScript Revealing Module Pattern Variable Scope

I have an issue where I can't get to a variable inside a function:
EDIT
I forgot to add that I am setting this workerPage.grid = $("#grid").data("kendoGrid"); on the jQuery $(function(){});
I can't use claimsGird variable inside the save function, I have to referenec it by workerPage.grid. Not the other variables like viewModel work fine. Here is the snippet:
save = function () {
saif.kendoGridUtils.addModifiedDataItems(
viewModel.CompanionClaims.Updated,
viewModel.CompanionClaims.Added,
$("#grid").data("kendoGrid").dataSource.data()
);
$.ajax({
url: $("#contentForm").attr("action"),
data: JSON.stringify(viewModel),
type: "POST",
contentType: "application/json"
}).success(function (data) {
//syncs viewModel with changes in model
$.extend(viewModel, kendo.observable(data));
//rebinds the grid data source
claimsGrid.dataSource.data(viewModel.CompanionClaims.Rows);
Here is the full script:
var workerPage = (function () {
var viewModel = kendo.observable(#Html.Raw(Json.Encode(Model))),
claimsGrid = null,
deleteFirm = function (firmModel) {
firmModel.Name = "";
firmModel.AttorneyName = "";
firmModel.Address.Line1 = "";
firmModel.Address.Line2 = "";
firmModel.Address.City = "";
firmModel.Address.State = "OR";
firmModel.Address.ZipCode = "";
firmModel.Address.PlusFourCode = "";
firmModel.PhoneNumber = "";
firmModel.FaxNumber = "";
firmModel.ContactName = "";
},
bind = function () {
kendo.bind($("#main-content"), viewModel);
},
save = function () {
saif.kendoGridUtils.addModifiedDataItems(
viewModel.CompanionClaims.Updated,
viewModel.CompanionClaims.Added,
$("#grid").data("kendoGrid").dataSource.data()
);
$.ajax({
url: $("#contentForm").attr("action"),
data: JSON.stringify(viewModel),
type: "POST",
contentType: "application/json"
}).success(function (data) {
//syncs viewModel with changes in model
$.extend(viewModel, kendo.observable(data));
//rebinds the grid data source
claimsGrid.dataSource.data(viewModel.CompanionClaims.Rows);
//rebinds view elements to view model so changes are visible
//kendo.bind($("#main-content"), viewModel);
bind();
// Errors and Warnings
var results = messageUtils.parseMessages(
viewModel.Messages.Errors,
viewModel.Messages.Informationals,
viewModel.Messages.Warnings
);
var errs = $("#errors").html(results.errorMessages);
$("#informationals").html(results.informationalMessages);
$("#warnings").html(results.warningMessages);
$.each(saif.kendoGridUtils.processErrors(viewModel.CompanionClaims.Rows), function (i, message) {
errs.html(errs.html() + message + "<br>");
});
// End Errors and Warnings
});
},
deleteRow = function () {
var row = claimsGrid.select(),
rowDataItem = claimsGrid.dataItem(row),
rowIndex = $(row).index(),
addedItemIndex = $.inArray(rowDataItem, viewModel.CompanionClaims.Added);
//add to Deleted if not new
if (addedItemIndex == -1 && $.inArray(rowDataItem, viewModel.CompanionClaims.Rows) != -1) {
viewModel.CompanionClaims.Deleted.push(rowDataItem);
}
//remove from Added if exists
if (addedItemIndex != -1) {
viewModel.CompanionClaims.Added.splice(addedItemIndex, 1);
}
claimsGrid.removeRow(row);
//select the next row, eg. if you delete row 2, select the row that took that rows poisition after it was deleted.
claimsGrid.select(claimsGrid.tbody.find(">tr:eq(" + rowIndex + ")"));
};
return {
bind: bind,
deleteFirm: deleteFirm,
deleteRow: deleteRow,
grid: claimsGrid,
save: save,
viewModel: viewModel
};
}());
The issue is that claimsGrid is never set to anything other than null. And setting workerPage.grid won't change the value of claimsGrid -- it's not a pointer, just a copy.
You'll instead have to use a getter/setter. With newer browsers/engines, that can be done with get and set:
// ...
return {
// ...
get grid() {
return claimsGrid;
},
set grid(grid) {
claimsGrid = grid;
},
// ...
};
You can also define grid as a function:
// ...
function getOrSetGrid(grid) {
if (typeof newGrid === 'undefined') {
return claimsGrid;
} else {
claimsGrid = grid;
}
}
return {
// ...,
grid: getOrSetGrid,
// ...
};
// ...
// rather than: workerPage.grid = ...;
workerPage.grid(...);
Or split it into getGrid and setGrid functions.
Scope in javascript works differently than other languages like Java or C#. In your case claimsGrid is not in scope for the save function. Does this help? http://coding.smashingmagazine.com/2009/08/01/what-you-need-to-know-about-javascript-scope/

Categories

Resources