I've decided to go back and relearn vanilla js since I've always relied on jquery.
I wanted to make some simple food order form.
My issue is that on my submit method, the "this" will refer to the form being submitted, rather than my placeOrder object.
When the order is submitted, I wanted to push it to my orders array which is inside cacheDom. Obviously, it can't use a push method on a form object.
can someone offer advice on how to go about this?
(function () {
var placeOrder = {
init: function() {
this.cacheDom();
this.bindEvents();
},
cacheDom : function() {
this.orderForm = document.getElementById('place-order-form');
this.elements = this.orderForm.elements;
this.orders = [];
},
bindEvents: function() {
if(this.orderForm.addEventListener){
this.orderForm.addEventListener("submit", this.submitOrder, false);
} else if (this.orderForm.attachEvent){
this.orderForm.attachEvent('onsubmit', this.submitOrder);
}
},
submitOrder: function(e) {
// this refers to the form being submitted
e.preventDefault();
var elements = this.elements;
var order = {};
for (var i = 0; i < elements.length; i++) {
if (elements[i].tagName !== 'BUTTON') {
if (elements[i].name === 'name') {
order.name = elements[i].value;
} else if (elements[i].name === 'food') {
order.food = elements[i].value;
}
}
}
//this.orders.push(order);
console.log(this);
}
};
placeOrder.init();
}());
Related
Inside my attemptSearch function, I use jQuery $.post to get some JSON results. I'm getting the error
Undefined is not an object
on the call
this.getSearchResults.bind(this)
I have the same set up in another web app and I don't get this error. What am I doing wrong?
var app = {
init: function() {
this.cacheDom();
this.bindEvents();
},
cacheDom: function() {
this.$search = $('#search');
},
bindEvents: function() {
this.$search.keyup(this.attemptSearch)
},
searchResults : [],
getSearchResults : function(val){
var currentSearchResult = val.query.search;
for (var i = 0; i < currentSearchResult.length; i++) {
var result = {
title: currentSearchResult[i].title,
}
console.log(this.searchResults);
this.searchResults.push(result);
}
},
attemptSearch: function(event) {
var wiki = "https://crossorigin.me/https://en.wikipedia.org/w/api.php?action=query&list=search&format=json&srsearch=";
if (event.keyCode == 13) {
$.post((wiki + $('#search').val()), this.getSearchResults.bind(this))
}
},
};
app.init();
You made sure to bind getSearchResults, but you didn't bind attemptSearch. That's almost surely the issue:
this.$search.keyup(this.attemptSearch.bind(this))
Lets say I have a javascript object with the the following
var Settings = function () {
this.timelimit = 0;
this.locked = false;
this.expires = null;
this.age = null;
};
And then I set some get/set functions like:
Settings.prototype = {
getAllAges: function () {
return self.age;
},
getTimeLimit: function () {
return self.timelimit;
},
load: function() {
data_from_local_storage = LoadLocalStorage();
}
}
In data_from_local_storage I have JSON variables that match the above variables (timelimit, locked etc .. )
Issue is, the object var settings_ref = Settings() have all these 4 variables - but also have these 3 functions assigned in settings_ref - due to this OO behavior I need to write inside the load() function:
this.timelimit = data_from_local_storage.timelimit
this.age = data_from_local_storage.age
this.locked = data_from_local_storage.locked
Because if I'll write
this = data_from_local_storage it will destroy my object.
So how can I avoid writing all these variables one-by-one ?
w/o a for loop inside a function
in this example are just 4 but there are much much more and I cannot write it everywhere everytime
I'm looking for some .update() function like in Python or something ..
Any quick shortcut that someone know ?
You can use Object.assign() in ES2015:
load: function() {
Object.assign(this, LoadLocalStorage());
}
It's apparently not supported yet in IE, but there's a polyfill on the MDN page:
if (typeof Object.assign != 'function') {
(function () {
Object.assign = function (target) {
'use strict';
// We must check against these specific cases.
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
};
})();
}
(Personally I would use Object.defineProperty() to add the method, but that's verbatim from MDN.)
(edit though I guess if you don't have Object.assign() you may not have Object.defineProperty() either :)
If you store the data inside another object literal, it makes persisting things to localstorage and back a lot easier.. Here is an example..
//pretend local storage loader
function LoadLocalStorage() {
return {
timelimit: 100,
locked: true,
expires: new Date(),
age:40
}
}
var Settings = function () {
this.data = {
timelimit: 0,
locked: false,
expires: null,
age:null
}
};
Settings.prototype = {
getAllAges: function () {
return this.data.age;
},
getTimeLimit: function () {
return this.data.timelimit;
},
load: function() {
this.data = LoadLocalStorage();
}
}
var settings = new Settings;
console.log('Age before our load');
console.log(settings.getAllAges());
settings.load();
console.log('Age after our load');
console.log(settings.getAllAges());
I have written the below jQuery functions to invoke filters on a datatable. But the "change" jQuery function seems to seems to invoke two times for a single click , hence, if i try to turn off one of the filter, it will immediately turn itself back on.
Function:
$(document).ready(function () {
var table = $('#example').DataTable({
rowReorder: {
selector: 'td:nth-child(2)'
},
responsive: true,
initComplete: function () {
var filters = $('div[id$=referalFilters]').find('input:checkbox');
filters.each(function () {
dict.push({
key: $(this).prop('name'),
value: $(this).prop('checked')
});
$(this).change(function () {
var currentName = $(this).prop('name');
for (var i = 0; i < dict.length; i++) {
if (dict[i].key == currentName) {
if (dict[i].value == true) {
dict[i].value = false;
} else {
dict[i].value = true;
}
}
}
for (var i = 0; i < dict.length; i++) {
if (dict[i].value == true) {
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
var currentStatus = data[3];
var found = false;
for (var i = 0; i < dict.length; i++) {
if (currentStatus == String(dict[i].key)) {
if (dict[i].value == true) {
found = true;
}
}
}
if (found) {
return true;
}
return false;
})
}
}
table.draw();
});
});
//this.api().columns().every( function () {
// var column = this;
//} );
}
});
});
I will appreciate, if someone could let me know what triggers the change function multiple times.
Here is the jsfiddle: https://jsfiddle.net/a81h11tc/ . In Js fiffle, the button seems to toggle but, the filtering does not happen.
As far as I can tell, your problem was an undefined variable, dict inside initComplete. After I added var dict = []; before var filters = ..., it worked like a charm. (My understanding is that you wanted the column Status to hide the rows that contain the value of the clicked filter. If you want the opposite, modify your code accordingly.) Here is a JSFiddle of the altered code, it should work the way you wanted.
TIP: By the way, remember to always check the browser's console as in most cases you can see the proper error messages for stuff like this (i.e. for your code it said RefereceError: dict is not defined, which was basically the only problem your code had).
Same as this question, but Prototype library specific:
I've got a Browser class, and I want to fire and observe custom events for this class. Prototype's custom event system only lets me bind to and fire events on DOM elements. Here's my first idea as to an alternative:
function Browser() {
this.event = new Element('span');
}
Browser.prototype.render = function() {
this.event.fire('browser:render');
}
var browser = new Browser();
browser.event.observe('browser:render', function() { ... });
Is there a better way to do this?
Thanks in part to Frits van Campen's advice, I made my own that serves my needs, a little more sophisticated than Frits' sample.
function EventManager(target) {
var target = target || window,
events = {};
this.observe = function(eventName, cb) {
if (events[eventName]) events[eventName].push(cb);
else events[eventName] = new Array(cb);
return target;
};
this.stopObserving = function(eventName, cb) {
if (events[eventName]) {
var i = events[eventName].indexOf(cb);
if (i > -1) events[eventName].splice(i, 1);
else return false;
return true;
}
else return false;
};
this.fire = function(eventName) {
if (!events[eventName]) return false;
for (var i = 0; i < events[eventName].length; i++) {
events[eventName][i].apply(target, Array.prototype.slice.call(arguments, 1));
}
};
}
Then I can do:
Function Browser() {
this.event = new EventManager(this);
}
Browser.prototype.render = function() {
this.event.fire("render");
}
browser = new Browser();
browser.event.observe("render", function() { alert("Rendered"); });
Why not just build your own event handling system? It really doesn't take much.
function MyClass() {
this.handlers = {};
}
MyClass.prototype.registerHandler = function(event, callback) {
this.handlers[event] = callback;
};
MyClass.prototype.fire = function(event) {
this.handlers[event]();
};
var instance = new MyClass();
instance.registerHandler('an event', function () {
alert('hi!');
});
instance.fire('an event');
I use the document to fire all my custom events. Works great.
document.on("customEvent:blah", this.doCustomEvent.bind(this));
document.fire("customEvent:blah");
I have the function below.
Everything works fine except for the Push, Pop and Remove method. These method should be called by the event-handler. This event is fired by the Google Maps API.
The problem is that when the event is fired, these method are not found. I have a "Push is not defined" error message.
I tried with this but that's not working.
How do I call the public method from the event handler?
function Track(mapContainer) {
var map = mapContainer;
var points = new Array();
var isEditMode = false;
var clickListener;
this.Push = function(point) { ... }
this.Pop = function() { ... }
this.Remove = function(point) { ... }
//Enable / disable the marker placements
this.PlaceWaypoint = function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { Remove(point); });
Push(point);
} else {
Pop();
}
});
}
}
}
You've got a closure/binding problem. One convention that is frequently used it to assign a variable called self of that, which can later be used in place of this, thanks to the closure properties of JS.
function Track(mapContainer) {
var map = mapContainer,
points = new Array(),
isEditMode = false,
clickListener,
// Make a variable self that points to this, that can be used inside closures
// where the original context is lost
self = this;
this.Push = function(point) { ... }
this.Pop = function() { ... }
this.Remove = function(point) { ... }
//Enable / disable the marker placements
this.PlaceWaypoint =
function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { Remove(point); });
// Use the closure reference self instead of this
self.Push(point);
} else {
// Use the closure reference self instead of this
self.Pop();
}
});
};
}
this always refers to the context of the current function, so if you use this in your event handler it refers to that function calls this, not the this in your Track function.
To create a closure that accesses the this of an outer scope, you need to assign that this to a new variable which can be accessed from the inner function:
var self = this;
this.PlaceWaypoint = function(isPlacing) {
// ...
self.Pop();
// ...
}
First of all Pop and Push is not global, second this in the inner scope has another meaning. So you can use closure and rename the "this" to variable of more global scope.
function Track(mapContainer) {
//....
var $this = this;
//Enable / disable the marker placements
this.PlaceWaypoint = function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { $this.Remove(point); });
$this.Push(point);
} else {
$this.Pop();
}
});
}
}
}