Why is this prototype failing? - javascript

function _(e) {
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
return document.getElementById(e.slice(1));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
return (c.length==1)?c[0]:c;
} else {
var t = document.getElementsByTagName(e);
return (t.length==1)?t[0]:t;
}
} else {
console.log('Error. Not a valid string in _.');
}
}
_.prototype.hide = function() {
//testing
console.log(this)
}
The function works fine, but when i try to add the method hide and try and call it like _('#menu').hide();, it throws the error: TypeError: Object #<HTMLDivElement> has no method 'hide' What have I misunderstood?
And yes, I did google this problem, I just don't get it. A hint would be much appreciated.

The constructor function needs to return itself (return this;). Currently it returns a DOM object, or undefined if no string is passed.
Try this:
function _(e) {
if (!(this instanceof _)){
return new _(e);
}
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
this.el = document.getElementById(e.slice(1));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
this.el = (c.length==1)?c[0]:c;
} else {
var t = document.getElementsByTagName(e);
this.el = (t.length==1)?t[0]:t;
}
return this;
} else {
console.log('Error. Not a valid string in _.');
throw e + ' is not a valid string';
}
}
_.prototype.hide = function() {
console.log(this);
}
You can invoke the constructor like so:
e = _('#myDiv');
e.hide();

You use a constructor function as a regular function, so it won't create an object, it will just return what you specified.
You can use it as a regular function, but then you need to call itself as a constructor to create the object to return, and handle when it's used as a constructor:
function _(e) {
if (!this instanceof _) {
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
return new _(document.getElementById(e.slice(1)));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
return new _((c.length==1)?c[0]:c);
} else {
var t = document.getElementsByTagName(e);
return new _((t.length==1)?t[0]:t);
}
} else {
console.log('Error. Not a valid string in _.');
}
} else {
this.elements = e;
}
}
You might consider to always use an array for the elements, even when it's a single element. Right now the elements property will either be an element or an array of elements, so you have to check for this every time you use it...

Try to define your function like this:
var _ = function (e) { };
EDIT
And yes don't forget to return this of course.

Related

The right way to check arguments data types

I wrote the check of argument's data types inside function body. But I want to create a common function for the check of data types, which will be used in several other functions.
It’s my constructor of “Cat” object:
function Cat(name, age, owner) {
if (!checkArgsDataTypes(arguments.callee, ["string", "number", "Man"])) {
throw new Error("Incorrect data type of arguments");
}
/* Continue function body if data types is correct */
}
Here creating the “Cat” object:
var my_cat = new Cat("Tom", 2, new Man());
And this my variant of the common check-function:
function checkArgsDataTypes(func_link, types_array) {
var i;
var current_data_type;
if (typeof func_link != "function") {
throw new Error("the first argument must be a function");
}
if (types_array instanceof Array) {
if (func_link.arguments.length != types_array.length) {
throw new Error("not matching count of arguments and count of array-type items");
}
if (types_array.length > 0) {
for (i = 0; i < types_array.length; i ++) {
if (typeof types_array[i] != "string") {
throw new Error("types_array items must have string data type");
}
}
}
else {
throw new Error("Types array is empty");
}
for (i = 0; i < types_array.length; i ++) {
current_data_type = types_array[i].toLowerCase();
/* check simple data types */
if (current_data_type == "string" || current_data_type == "object" || current_data_type == "number") {
if (typeof func_link.arguments[i] != current_data_type) {
return false;
}
}
/* check complex data types */
else {
if (func_link.arguments[i].constructor.name != types_array[i]) {
return false;
}
}
}
return true;
}
else {
throw new Error("the second argument must be an array");
}
}
I wanna know, what is more less and simply variant of the common function for check data types?
What are your suggestions? Thanks.

How do I change the "this" pointer in JavaScript?

I made an object like this:
var MyObj = function(arg)
{
var PublicMethods = {
SomeMethod: function(someArg)
{
if(SomeCheck(arg)
{
PublicMethods.SomeFunc2 = somethingElse;
}
}
};
return PublicMethods;
};
However MyObj doesn't seem to be persistent and between calls, PublicMethods doesnt preserve the new methods added to it, so I tried moving it to global scope, however then it doesn't recognize the 'args' passed from MyObj anymore.
If I make MyObj like this:
var MyObj = (function()
{
//..
}());
Then it becomes a persistent object, but I'm not sure - can I call it like a function again?
jQuery seems to have a persistent object and at the same time it can be called like a function, how do they achieve that?
I want to be able to do this:
MyObj("Something Here").SomeMethod("Something Else");
and thus to be able to create a SomeFunc2 method that I can later call too:
MyObj("Something Here").SomeFunc2("Something Else");
Simply store the result of the initial MyObj call in a local variable:
var obj = MyObj("Something Here");
obj.SomeMethod("Something Else");
obj.SomeFunc2("Something else");
The PublicMethods variable is specific to each call of MyObj, so when you call MyObj for the second time, you get a different instance of PublicMethods. By using a variable to store the result of the first MyObj call, you can use the same instance of PublicMethods for both the SomeMethod and SomeFunc2 functions.
As a side note, you may want to look into constructor functions which would allow you to define functions more simply, instead of returning an object. For example:
function Example() {
this.a = function() {
return "a";
};
this.b = function() {
this.a = function() {
return "b";
}
}
}
var example = new Example();
example.a(); // => "a"
example.b();
example.a(); // => "b"
You can create a jQuery method which extends jQuery or jQuery.fn and can also set this context within the method.
(function($) {
jQuery.addMethod = function addMethod({methodName, method, type}) {
let bool = {
[type]: false
};
let _jQuery_jQueryFn_ = Object.keys(bool).pop();
if (type === "jQuery") {
for (let prop in jQuery) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "fn") {
for (let prop in jQuery.fn) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "jQuery" && bool[_jQuery_jQueryFn_] === false) {
jQuery[methodName] = method;
}
if (type === "fn" && bool[_jQuery_jQueryFn_] === false) {
jQuery[type][methodName] = method;
}
if (bool[_jQuery_jQueryFn_] === true) {
return Promise.reject(
new ReferenceError(
methodName
+ " previously defined at "
+ _jQuery_jQueryFn_
));
} else {
console.log(methodName + " defined at " + _jQuery_jQueryFn_);
}
return {methodName:methodName, type};
}
})(jQuery);
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "jQuery"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery[type][methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (methodName === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery("span")[methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve(
$.addMethod({
methodName: "reverseText",
method: function reverseText(_text, context) {
let text = [...(_text || this.text())].reverse().join("");
(context || this).text(text);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName]()
} else {
if (methodName in window["jQuery"][type]) {
// set context `this` to `span`
let span = jQuery("section")[methodName]("321", $("span"))
.css("color", "sienna");
console.log(
span.is(document.querySelector("span"))
);
jQuery("section")[methodName]()
.css("color", "green");
}
}
})
.catch(function(err) {
console.error(err)
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js">
</script>
<section>section</section>
<span>span</span>

Convert javascript class instance to plain object preserving methods

I want to convert an instance class to plain object, without losing methods and/or inherited properties. So for example:
class Human {
height: number;
weight: number;
constructor() {
this.height = 180;
this.weight = 180;
}
getWeight() { return this.weight; }
// I want this function to convert the child instance
// accordingly
toJSON() {
// ???
return {};
}
}
class Person extends Human {
public name: string;
constructor() {
super();
this.name = 'Doe';
}
public getName() {
return this.name;
}
}
class PersonWorker extends Person {
constructor() {
super();
}
public report() {
console.log('I am Working');
}
public test() {
console.log('something');
}
}
let p = new PersonWorker;
let jsoned = p.toJSON();
jsoned should look like this:
{
// from Human class
height: 180,
weight: 180,
// when called should return this object's value of weight property
getWeight: function() {return this.weight},
// from Person class
name: 'Doe'
getName(): function() {return this.name},
// and from PersonWorker class
report: function() { console.log('I am Working'); },
test: function() { console.log('something'); }
}
Is this possible to achieve, and if so, how?
In case you're wondering, I need this because I am using a framework that, unfortunately, accepts as input only an object, whereas I am trying to use TypeScript and class inheritance.
Also, I am doing the above conversion once so performance isn't an issue to consider.
The solutions consisting of iterating through object properties will not work if the compiler's target option is set to es6. On es5, the existing implementations by iterating through object properties (using Object.keys(instance)) will work.
So far, I have this implementation:
toJSON(proto?: any) {
// ???
let jsoned: any = {};
let toConvert = <any>proto || this;
Object.getOwnPropertyNames(toConvert).forEach((prop) => {
const val = toConvert[prop];
// don't include those
if (prop === 'toJSON' || prop === 'constructor') {
return;
}
if (typeof val === 'function') {
jsoned[prop] = val.bind(this);
return;
}
jsoned[prop] = val;
const proto = Object.getPrototypeOf(toConvert);
if (proto !== null) {
Object.keys(this.toJSON(proto)).forEach(key => {
if (!!jsoned[key] || key === 'constructor' || key === 'toJSON') return;
if (typeof proto[key] === 'function') {
jsoned[key] = proto[key].bind(this);
return;
}
jsoned[key] = proto[key];
});
}
});
return jsoned;
}
But this is still not working. The resulted object includes only all the properties from all classes but only methods from PersonWorker.
What am I missing here?
Lots of answers already, but this is the simplest yet by using the spread syntax and de-structuring the object:
const {...object} = classInstance
This is what's working for me
const classToObject = theClass => {
const originalClass = theClass || {}
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass))
return keys.reduce((classAsObj, key) => {
classAsObj[key] = originalClass[key]
return classAsObj
}, {})
}
Ok, so the implementation in my OP was wrong, and the mistake was simply stupid.
The correct implementation when using es6 is:
toJSON(proto) {
let jsoned = {};
let toConvert = proto || this;
Object.getOwnPropertyNames(toConvert).forEach((prop) => {
const val = toConvert[prop];
// don't include those
if (prop === 'toJSON' || prop === 'constructor') {
return;
}
if (typeof val === 'function') {
jsoned[prop] = val.bind(jsoned);
return;
}
jsoned[prop] = val;
});
const inherited = Object.getPrototypeOf(toConvert);
if (inherited !== null) {
Object.keys(this.toJSON(inherited)).forEach(key => {
if (!!jsoned[key] || key === 'constructor' || key === 'toJSON')
return;
if (typeof inherited[key] === 'function') {
jsoned[key] = inherited[key].bind(jsoned);
return;
}
jsoned[key] = inherited[key];
});
}
return jsoned;
}
Here is the implementation for the toJSON() method. We are copying over the properties & methods from the current instance to a new object and excluding the unwanted methods i.e. toJSON and constructor.
toJSON() {
var jsonedObject = {};
for (var x in this) {
if (x === "toJSON" || x === "constructor") {
continue;
}
jsonedObject[x] = this[x];
}
return jsonedObject;
}
I have tested the object returned by toJSON() in Chrome and I see the object behaving the same way as you are expecting.
I'm riffing on Alex Cory's solution a lot, but this is what I came up with. It expects to be assigned to a class as a Function with a corresponding bind on this.
const toObject = function() {
const original = this || {};
const keys = Object.keys(this);
return keys.reduce((classAsObj, key) => {
if (typeof original[key] === 'object' && original[key].hasOwnProperty('toObject') )
classAsObj[key] = original[key].toObject();
else if (typeof original[key] === 'object' && original[key].hasOwnProperty('length')) {
classAsObj[key] = [];
for (var i = 0; i < original[key].length; i++) {
if (typeof original[key][i] === 'object' && original[key][i].hasOwnProperty('toObject')) {
classAsObj[key].push(original[key][i].toObject());
} else {
classAsObj[key].push(original[key][i]);
}
}
}
else if (typeof original[key] === 'function') { } //do nothing
else
classAsObj[key] = original[key];
return classAsObj;
}, {})
}
then if you're using TypeScript you can put this interface on any class that should be converted to an object:
export interface ToObject {
toObject: Function;
}
and then in your classes, don't forget to bind this
class TestClass implements ToObject {
toObject = toObject.bind(this);
}
This solution will lose methods, but it is a very simple solution to convert a class instance to an object.
obj = JSON.parse(JSON.stringify(classInstance))
using Lodash
This method isn't recursive.
toPlainObject() {
return _.pickBy(this, item => {
return (
!item ||
_.isString(item) ||
_.isArray(item) ||
_.isNumber(item) ||
_.isPlainObject(item)
);
});
}

apply parameter into function within parameter array

http://jsbin.com/ukizof/1/
how do you call a function which is part of a array and set a paramater to it , as in the example below i want the script to return a function in order to call a function parameter which as below in the example is set below.
var bQuery = {
one: function(elem, options) {
options = this.extend({
method: 'html',
event: 'test2',
func:null
}, options || {});
var element = elem;
if (options.method == 'html') {
element.innerHTML = options.event;
} else if (options.method == 'append') {
element.innerHTML = element.innerHTML + options.event;
} else if (options.method == 'prepend') {
element.innerHTML = options.event + element.innerHTML;
}
return // return method to apply string to func: parameter function as below is "e"
},
extend: function(a, b) {
for (var prop in b) {
a[prop] = b[prop];
}
return a;
}
};
$ = bQuery;
$.one(document.getElementById("log"), {
method: 'append',
event: 'rjfjfjjffj',
func: function(e){
alert(e);
}
});
If I understood you right, you want
var options, element;
…
return function() {
if (typeof options.func == 'function')
return options.func(element.innerHTML);
};

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

Categories

Resources