Method for getting jQuery element or value - javascript

I have set up a class that allows me to get an input/select element's value or the element itself:
My.Class = {
property: function(e){
var t = '#element_id';
return My.Helper.fetch(t, e);
}
}
My.Helper = {
fetch: function(t,e){
if (e == true) {
return jQuery(t);
};
return jQuery(t).val();
}
}
var element = My.Class.property(true);
var elementValue = My.Class.property();
This works great, but when I have dozens of properties, the code repeats quite a bit:
My.Class = {
property: function(e){
var t = '#element_id';
return My.Helper.fetch(t, e);
},
property_b: function(e){
var t = '#element_id_b';
return My.Helper.fetch(t, e);
},
property_c: function(e){
var t = '#element_id_c';
return My.Helper.fetch(t, e);
},
property_d: function(e){
var t = '#element_id_d';
return My.Helper.fetch(t, e);
}
}
How can I can I construct these properties without having to repeat myself so often? Keep in mind that my example property names and element names cannot always be consistent.

This seems like you should just have a get_property(selector_string) method that does this for you. The other alternative is that on instantiation the object could get a list of elements that you'd be able to 'reget' if the DOM's changed.

Related

Javascript & knockoutjs: how to refactor the following code to be able to access the properties outside the function

Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

Issues with event delegation on same container

I have a function, simplified like this:
var fooFunction = function($container, data) {
$container.data('foobarData', data);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
if (typeof data.validateFunction === 'function' && !data.validateFunction(e)) {
return;
}
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(data.returnText);
});
};
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-companies').length ? true : false;
},
returnText: 'Hello Company!',
});
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-humans').length ? true : false;
},
returnText: 'Hello Human!',
})
I am using event delegation on the same container (.same-container) with a custom validateFunction() to validate if the code in // Do stuff should run.
For each fooFunction() initiation, I have some different logic that will get called on // Do stuff. The issue is that those two event delegations conflict. It seems that only one of them is called and overwrites the other one.
How can I have multiple event delegations with the option to define via a custom validateFunction which one should be called. I use preventDefault() + stopPropagation() so on click on a <a>, nothing happens as long as validateFunction() returns true.
The problem is that you're overwriting $(e.delegateTarget).data('foobarData') every time.
Instead, you could add the options to an array, which you loop over until a match is found.
var fooFunction = function($container, data) {
var oldData = $container.data('foobarData', data);
if (oldData) { // Already delegated, add the new data
oldData.push(data);
$container.data('foobarData', oldData);
} else { // First time, create initial data and delegate handler
$container.data('foobarData', [data]);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
var index = data.find(data => typeof data.validateFunction === 'function' && !data.validateFunction(e));
if (index > -1) {
var foundData = data[index]
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(foundData.returnText);
}
});
}
}

Chaining function not work when inside another function

I try to create chaining function using vanilla javascript, its work if just chaining, but if inside other function its stop working.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
},
$ = function (el) {
this.el = M$(el);
// event function
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.el.length - 1; i >= 0; i--) {
fn.call(val, i, this.el[i]);
}
return this;
}
if(this instanceof $) {
return this.$;
} else {
return new $(el);
}
};
//use
$("button").forEach(function(index, el)
// when i use function event, its not work
el.event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
My question is, how to be event function working inside forEach function?
Thanks for help!
First off, there is an issue with brackets in your code after $("button").forEach(function(index, el) you are missing {;
Then the problem is that when you try to call click-callback on your elements (buttons), in fact, due to the this issues the elements (buttons) don't have event() property. They are not even defined themselves since this.el = M$(el); goes outside forEach(). I tweaked and cleaned a little your code, check it out. I guess now it does what you want:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]); else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function(el) {
this.forEach = function(fn,val) {
// assign this.el and this.el[i].event inside forEach(), not outside
this.el = M$(el);
for(var i = this.el.length - 1; i >= 0; i--) {
this.el[i].event = function(type,fn) { this.addEventListener(type,fn,false); };
fn.call(val, i, this.el[i]);
}
}
return this;
};
$("button").forEach(function(index, el) {
el.event("click", function() { alert("hello, " + this.textContent); });
});
<button>btn1</button>
<button>btn2</button>
UPDATE
While the previous solution is fine for the particular purpose of setting click handlers on buttons, I think what you really want is to emulate Jquery and chain function calls. I improved your attempt right in this way:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]);else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function (el) { //console.log(this);
this.el = M$(el);
this.event = function(type,fn) {
for(var i = this.el.length - 1; i >= 0; i--) this.el[i].addEventListener(type,fn,false);
}
this.forEach = function(fn) {
fn.call(this);
}
return this;
};
$("button").forEach(function() {
this.event("click", function() {
alert("hello, " + this.textContent);
});
});
<button>btn1</button>
<button>btn2</button>
The key to understanding here is that your this object should always be equal to $ {el: HTMLCollection(2), event: function, forEach: function}. So,
calling $("button") you initially set it to $ {el: HTMLCollection(2), event: function, forEach: function} - with HTML Collection and event&forEach functions;
calling $("button").forEach(fn) you keep forEach's context equal to this from previous step;
calling fn.call(this); inside forEach() you call your callback fn and pass the same this to it;
inside the callback fn you call this.event() - it works because your this is always the one from the first step.
in this.event() which is just $.event() we just traverse our HTMLCollection and set handlers for click event on buttons. Inside $.event() this will be equal to a button element because we call it in such a context on click event, so, this.textContent takes the buttons' content.
Thanks, really good question!
First things first.
1.
this.el = M$(el);
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
}
As you defined M$ you can either have a HtmlCollection if you get elements by tag name or by class name or just one element if you get element by id.
Then you suppose that your el is one when it can be a collection.
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
You probably receive a collection if you try to get all buttons.
2.
If you try to run posted code you will receive an Unexpected identifier error because you missed a { after forEach(function(index, el).
3.
If you put that { in there you will receive a el.event is not a function error because you don't have an event function on el, but you have that on $(el).
4.
If you change your code to:
$("button").forEach(function(index, el)
{
// when i use function event, its not work
$(el).event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
You'll receive an error because you didn't handled multiple elements. See 1 problem.
Have a look at this.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return Array.apply([],[doc.getElementById(m[1])]);
} else if(m[2]) {
return Array.apply([],doc.getElementsByTagName(m[2]));
} else if(m[3]) {
return Array.apply([],doc.getElementsByClassName(m[3]));
}
},
$ = function (el) {
if(! (this instanceof $)) {
return new $(el);
}
this.els = M$(el);
// event function
this.event = function(type,fn) {
this.forEach(function(index, el){
el.addEventListener(type,fn,false);
});
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.els.length - 1; i >= 0; i--) {
fn.call(val, i, this.els[i]);
}
return this;
}
return this;
};
//use
$("button").event("click", function() {
alert("hello");
});
Here the M$ function is made to return an array to keep things consistent.
So, the $().event function is changed to iterate through all the elements in this.els.
Hence, you could simply call $("button").event function instead of $("button").forEach function to register event listeners.
Refer: Demo
This one works. But, Is this what you want? I am not sure.

Can i get all attributes that begin with "on" using jquery?

I am looking for a way to get all the attributes of an element that begins with "on" using jQuery or Vanilla JS. I am currently getting all attributes and then looping through them to get the ones I want using the method proposed by #primvdb on this post: Get all attributes of an element using jQuery.
My code looks like this:
/* Expanding .attr as proposed by #primvdb */
(function(old) {
$.fn.attr = function() {
if(arguments.length === 0) {
if(this.length === 0) {
return null;
}
var obj = {};
$.each(this[0].attributes, function() {
if(this.specified) {
obj[this.name] = this.value;
}
});
return obj;
}
return old.apply(this, arguments);
};
})($.fn.attr);
/* And then my function */
$.fn.attrThatBeginWith = function(begins){
var attributes = this.attr();
var attrThatBegin = {};
for(var attr in attributes){
if(attr.indexOf(begins)==0){
attrThatBegin[attr] = attributes[attr];
}
}
return attrThatBegin;
};
/* Usage */
var onAttributes = $("#MyElement").attrThatBeginWith("on");
And this works but is very "dirty". It's seems like with all the vast features of jQuery there should be a better "cleaner" way to do this. Does anybody have any suggestions?
You can get all attributes attached to an element with element.attributes.
The native attributes object can be converted to an array and then filtered based on the given string.
A plugin that does the above would look like
$.fn.attrThatBeginWith = function(begins){
return [].slice.call(this.get(0).attributes).filter(function(attr) {
return attr && attr.name && attr.name.indexOf(begins) === 0
});
};
FIDDLE

Overriding parent prototype method for event handling

I am using the following code jsFiddle to work with form fields and events. I have previously asked two questions regarding this and they have helped me tremendously. Now I have a new problem/question.
function Field(args) {
this.id = args.id;
this.elem = document.getElementById(this.id);
this.value = this.elem.value;
}
Field.prototype.addEvent = function (type) {
this.elem.addEventListener(type, this, false);
};
// FormTitle is the specific field like a text field. There could be many of them.
function FormTitle(args) {
Field.call(this, args);
}
Field.prototype.blur = function (value) {
alert("Field blur");
};
FormTitle.prototype.blur = function () {
alert("FormTitle Blur");
};
Field.prototype.handleEvent = function(event) {
var prop = event.type;
if ((prop in this) && typeof this[prop] == "function")
this[prop](this.value);
};
inheritPrototype(FormTitle, Field);
var title = new FormTitle({name: "sa", id: "title"});
title.addEvent('blur');
function inheritPrototype(e, t) {
var n = Object.create(t.prototype);
n.constructor = e;
e.prototype = n
}
if (!Object.create) {
Object.create = function (e) {
function t() {}
if (arguments.length > 1) {
throw new Error("Object.create implementation only accepts the first parameter.")
}
t.prototype = e;
return new t
}
}
The problem is that I want to override the parent method (Field.prototype.blur) and instead use FormTitle.prototype.blur method for the title object. But the object keeps referencing the parent method and the alert always shows 'Field blur' instead of 'FormTitle Blur'. How can I make this work?
You are defining a method in the FormTitle prototype, then replacing the whole prototype with another object using inheritPrototype.
You have to swap the order. First you call this:
inheritPrototype(FormTitle, Field);
Then you set onblur on the prototype object you just created:
FormTitle.prototype.blur = function () {
alert("FormTitle Blur");
};
http://jsfiddle.net/zMF5e/2/

Categories