JavaScript Revealing Module Pattern Variable Scope - javascript

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/

Related

Javascript not recognizing self.function when inside onclick event

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

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 access knockout observables from different object scopes

My data model is consisting of two objects; project and task.
I load my data from the db via json and MVC-services and map my observableArrays like this:
viewModel = function () {
var self = this;
// some code...
// projects
self.Projects = ko.observableArray();
var mappedProjects = [];
$.ajax({
url: "myService/GetProjectsByUserId",
data: "userID=" + meID,
dataType: 'json',
async: false,
success: function (allData) {
mappedProjects = $.map(allData, function (item) {
return new Project(item);
});
}
});
self.Projects(mappedProjects);
// tasks
self.Tasks = ko.observableArray();
var mappedTasks = [];
$.ajax({
url: "myService/GetTasksByUserID",
data: "userid=" + meID,
dataType: 'json',
async: false,
success: function (allData) {
mappedTasks = $.map(allData, function (item) {
return new Task(item, self.Projects); // is there a smarter way to access self.Projects from the Scene prototype?
//return new Task(item);
});
}
});
self.Tasks(mappedTasks);
//some more code...
};
where
Project = function (data) {
this.projectID = data.projectID;
this.type = ko.observable(data.type);
};
Task = function (data, projects) {
this.taskID = data.taskID;
this.projectID = data.projectID;
//this.projecttype = ??? simpler solution?
this.projecttype = ko.computed(function () { // Is there a simpler way to access 'viewModel.Projects' from within 'Task'?
var project = ko.utils.arrayFirst(projects, function (p) {
return p.projectID === self.projectID;
});
if (!project) {
return null;
}
else {
return project.headerType();
}
});
};
The thing is (as you see) I want to access the projectType inside the Task-object. Is there a simpler way to do this than instantiating the object with the self.Projects as input?
Could self.Projects be bound when defined in some way so I could access it via the DOM?
From your comments, it looks like that you have multiple view models dependent on Task and Project objects. For decoupling between components, i would say to use ko.postbox plugin. You can easily have synchronization between viewmodels and non-knockout components using publishOn and subscribeTo extensions.
So your Task object will subscribe to Projects observableArray in viewModel like
Task = function (data) {
this.taskID = data.taskID;
this.projectID = data.projectID;
var projects = ko.observableArray().subscribeTo("projectsLoaded",true);
//this.projecttype = ??? simpler solution?
this.projecttype = ko.computed(function () { // Is there a simpler way to access 'viewModel.Projects' from within 'Task'?
var project = ko.utils.arrayFirst(projects(), function (p) {
return p.projectID === self.projectID;
});
if (!project) {
return null;
}
else {
return project.headerType();
}
});
};
and in your viewModel, you just have to make Projects observable array publish "projectsLoaded" topic/event. Like
viewModel = function () {
var self = this;
// some code...
// projects
self.Projects = ko.observableArray().publishOn("projectsLoaded");
// ....
}
Whenever the projects array changes in viewModel, you will always have the latest value in Task project array.
JsFiddle: http://jsfiddle.net/newuserjs/wffug341/3/

Knockout.js How should I add computed value to observaleArray?

I need to add to observableArray computed values, my current code is look like this:
self.prices = ko.observableArray();
.....
var pax = $("select#ticketsnumber option:selected").val();
var irt;
if ($("input[name='isrt']").is(":checked")) {
irt = 1;
} else {
irt = 0;
}
$.each(self.prices(), function (price) {
price.FinalPrice = ko.computed(function() {
return prices.Price * irt * parseInt(pax);
});
});
But I do not have any idea how should I call binding of this computed value (currently this way - <span name="totalprice" data-bind="text: ko.utils.unwrapObservable($data.FinalPrice)">) as well as this just seemed like computed value has not been added - binding result show 0.
Data model:
public class PriceItem
{
...
public string Price { get; set; }
...
public int FinalPrice { get; set; }
}
This is the way how I retrieve data to self.prices:
self.getprices = function () {
var destinationtypefrom = $("select#optionsfrom option:selected").attr("name");
var destinationtypeto = $("select#optionsto option:selected").attr("name");
var fromcode = $("select#optionsfrom").val();
var tocode = $("select#optionsto").val();
var json = { desttypefrom: destinationtypefrom, desttypeto: destinationtypeto, codefrom: fromcode, codeto: tocode, 'DepartureDate': $("#departure").val(), 'ReturnDate': $("#return").val() };
$.ajax({
url: "/Home/GetFlights",
data: json,
type: "post",
cache: false,
success: function (result) {
if (result.Error == null) {
self.prices(result);
ko.mapping.fromJS(result, {}, self.prices);
} else {
$("#modalerror").on("show.bs.modal", function () {
var modal = $(this);
modal.find("#errormsg").text(result.Error);
});
}
},
error: function (xhr) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
}
});
}
The main problem is that your $.each is wrong by assuming the first argument to be the actual element in the prices array. In fact, the first argument is the index, the second argument is the actual price you want to augment.
Also, you seem to have a typo in the computed function calculation, it's price.Price instead of prices.Price.
Try this:
$.each(self.prices(), function (index, price) {
price.FinalPrice = ko.computed(function() {
return price.Price * irt * parseInt(pax);
});
});
And in your HTML, something like:
<div data-bind="foreach: prices">
<span name="totalprice" data-bind="text: FinalPrice"></span>
</div>
See Fiddle

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());

Categories

Resources