2

i'm trying to run my JS code in differend areas independently, so i wanted to create a class and found this article and my problem is, that I can't use functions, which are in my class in my class.

Here is my JS Code:

window.addEventListener('load', tagEditorsInit);
function tagEditorsInit() {
    var tagEditors;
    var tagLists = document.getElementsByClassName('tagList');
    for(var i = 0; i < tagLists.length; i++) {
        tagEditors[i] = new function() {
            var edit, editTag, tagList, tagInput, tagOutput, i;
            this.init = new function() {
                edit = false;
                tagList = document.querySelectorAll('[class=tagList]').item(i); //L.96/97 #1
                tagInput = tagList.querySelectorAll('[name=tagInput]');
                tagOutput = tagList.querySelectorAll('[name=tags]');
                tagInput.addEventListener('keyup', tagOnKeyPress(event.keyCode)); //13-ERROR
                tagList.addEventListener('mouseout', function() {if(tagList.ownerDocument.activeElement.parentNode !== tagList && !isHover(tagList)) {tagList.style.maxHeight = '40px';}});
                tagInput.addEventListener('focus', changeSize);
                tagInput.addEventListener('blur', changeSize);
                for(var i = 2; i < tagList.childNodes.length; i++) {
                    tagList.childNodes[i].addEventListener('click', tagOnClick);
                    tagList.childNodes[i].addEventListener('mousemove', tagOnMouseMove);
                    tagList.childNodes[i].addEventListener('mouseout', tagOnMouseOut);
                }
            };
            this.tagOnKeyPress = new function(keyCode) {
                var tagInputValue = tagInput.value.trim();
                if(tagInputValue.length !== 0) {
                    if(edit) {
                        if(keyCode === 13) {
                            edit = false;
                            editTag.style.borderColor = '#ddd';
                            tagInput.value = '';
                            return;
                        }
                        tagOutput.value = tagOutput.value.replace(editTag.innerHTML,tagInputValue);
                        newTag = editTag;
                    } else {
                        if(keyCode !== 13) return;
                        tagOutput.value = tagOutput.value + tagInputValue + ';';
                        var newTag = document.createElement('div');
                        newTag.addEventListener('click', tagOnClick);
                        newTag.addEventListener('mousemove', tagOnMouseMove);
                        newTag.addEventListener('mouseout', tagOnMouseOut);
                    }
                    newTag.innerHTML = tagInputValue;
                    tagList.appendChild(newTag);
                }
                if(!edit) tagInput.value = '';
            };
            this.tagOnClick = new function() {
                if((this.offsetWidth + getOffsetLeft(this) - event.pageX) < parseInt(this.style.backgroundSize, 10)) {
                    tagOutput.value = tagOutput.value.replace(this.innerHTML + ';','');
                    this.parentNode.removeChild(this);
                    tagInput.value = '';
                    edit = false;
                } else {
                    setEdit(this);
                }
                tagInput.focus();
            };
            this.tagOnMouseMove = new function() {
                if((this.offsetWidth + getOffsetLeft(this) - event.pageX) < parseInt(this.style.backgroundSize, 10)) {
                    this.style.backgroundSize = '16px';
                } else {
                    this.style.backgroundSize = '18px';
                }
            };
            this.tagOnMouseOut = new function() {
                this.style.backgroundSize = '18px';
            };
            this.setEdit = new function(tag) {
                edit = true;
                editTag = tag;
                tag.style.borderColor = '#297CCF';
                tagInput.value = tag.innerHTML;
            };
            this.changeSize = new function(e) {
                if(e.type === 'focus') {
                    tagList.style.maxHeight = 'none';
                } else if(e.type === 'blur' && !isHover(tagList)) {
                    tagList.style.maxHeight = '40px';
                }
            };
            this.isHover = new function(elem) {
                return (elem.parentElement.querySelector(':hover') === elem);
            };
            this.getOffsetLeft = new function(elem) {
                var offsetLeft = 0;
                while(true) { //521px over while scrolling to the right ---> (-TABLE:521px)
                    if(elem.nodeName !== 'TABLE') offsetLeft += elem.offsetLeft;
                    elem = elem.parentNode;
                    if(elem.nodeName === 'HTML') break;
                }
                return offsetLeft;
            };
        };
        //tagEditors[i].tagList = tagLists.item(i); Why isn't it working??? #1
        tagEditors[i].i = i;
        tagEditors[i].init();
    }
}

I'm getting this error message:

13: Uncaught ReferenceError: tagOnKeyPress is not defined

  • 13: (anonymous function)

  • 8 : (anonymous function)

  • 6 : tagEditorsInit

Questions:

  • Can I fix this? ---> How?
  • Is there a better way to do this?

JSFiddle

Thank you! -Minding

8
  • See Javascript: Do I need to put this.var for every variable in an object? Also, you should not use new function(){…} to instantiate objects! Commented Apr 16, 2015 at 17:08
  • tagInput = tagList.querySelectorAll('[name=tagInput]'); gives you a NodeList array. You can't bind an eventListener to a NodeList. You have to loop to each node. Commented Apr 16, 2015 at 17:12
  • @Daved I think the original code takes the first object of the NodeList, cause 'tagInput = tagList.querySelectorAll('[name=tagInput]').item(0);', don't change anything and 'tagInput = tagList.querySelectorAll('[name=tagInput]').item(1);' throws this error message: Uncaught TypeError: Cannot read property 'addEventListener' of null Commented Apr 16, 2015 at 17:18
  • @Bergi Deleting 'this' and 'new' from the function throws this error message: Uncaught ReferenceError: tagOnKeyPress is not defined Commented Apr 16, 2015 at 17:21
  • Yes but querySelectorAll returns a NodeList. Change it to tagInput = tagList.querySelectorAll('[name=tagInput]')[0]; if you want the first node. Commented Apr 16, 2015 at 17:23

3 Answers 3

3

Call the function preceded by this, which refers to the current scope (in this case, the object of the "class" you created, which is where your tagOnKeyPress function is defined).

tagInput.addEventListener('keyup', this.tagOnKeyPress(event.keyCode));

Note, there are other function calls in your code that will require this change as well.

Here is a simple example of calling an object's function from inside the object:

function MyClass() {
    this.firstFunc = function() {
        console.log('first function');
    };
    
    this.secondFunc = function() {        
        console.log('second function calls:');
        this.firstFunc();
    };
};

var obj = new MyClass();
obj.firstFunc();
obj.secondFunc();

If you look at the console, you will see the following output:

first function
second function calls:
first function

Sign up to request clarification or add additional context in comments.

7 Comments

Error: undefined is not a function :/
@Minding What line is that occurring on?
Sorry, but my code is still not working :( - Check out my JSFiddle
@Minding I am looking at your fiddle, and I don't see any errors. What action are you performing that causes the errors? Typing in a textbox? Deleting one of the tags? What?
The first two are the original ones, but I they can handle only one "Tag Editor", but I want multiple of them (Multiple Edition in the JSFiddle). The Code i posted here is from the Multiple Edition and is still not working.
|
1

Okay, to first address a couple things:

1.This is not the ideal approach to creating objects, which is what you are looking to do. In the example, I have modified it a bit, but I would still rewrite it using the prototype approach. What you have here will create these functions for each instance of the object, loading way more than you need to into memory.

2.On the textareas with the onKeyPress, you cannot reference the tagOnKeyPress function that is part of the tagEditor object. It's not defined in the public scope.

3.When adding eventListeners, don't execute the function in the definition for the handler unless you are returning a function as a handler.

tagInput.addEventListener('keyup', tagOnKeyPress(event.which)); 

tries to execute "tagOnKeyPress" immediatley. Instead, pass just the function reference, and expect an "event" type passed to it. Then, define the keyCode in the function.

tagInput.addEventListener('keyup', this.tagOnKeyPress); //13-ERROR

this.tagOnKeyPress = function(event) {
    var keyCode = event.which;
}

4.As someone else pointed out, when you are referencing the functions defined within the scope of a function (your object), you need to use "this" as a prefix.

I changed the code a bit to define a "TagEditor" object for use with your loop. You could add the functions to the TagEditor prototype to increase performance and extensibility.

Code:

var TagEditor = function () {
    var edit, editTag, tagList, tagInput, tagOutput, i;
    this.init = function () {
        edit = false;
        tagList = document.querySelectorAll('[class=tagList]').item(i); //L.96/97 #1
        tagInput = tagList.querySelectorAll('[name=tagInput]')[0];
        tagOutput = tagList.querySelectorAll('[name=tags]');
        tagList.addEventListener('mouseout', function () { if (tagList.ownerDocument.activeElement.parentNode !== tagList && !isHover(tagList)) { tagList.style.maxHeight = '40px'; } });
        tagInput.addEventListener('keyup', this.tagOnKeyPress); //13-ERROR
        tagInput.addEventListener('focus', this.changeSize);
        tagInput.addEventListener('blur', this.changeSize);
        for (var i = 2; i < tagList.childNodes.length; i++) {
            tagList.childNodes[i].addEventListener('click', this.tagOnClick);
            tagList.childNodes[i].addEventListener('mousemove', this.tagOnMouseMove);
            tagList.childNodes[i].addEventListener('mouseout', this.tagOnMouseOut);
        }
    };
    this.tagOnKeyPress = function (e) {
        var keyCode = e.which;
        var tagInputValue = tagInput.value.trim();
        if (tagInputValue.length !== 0) {
            if (edit) {
                if (keyCode === 13) {
                    edit = false;
                    editTag.style.borderColor = '#ddd';
                    tagInput.value = '';
                    return;
                }
                tagOutput.value = tagOutput.value.replace(editTag.innerHTML, tagInputValue);
                newTag = editTag;
            } else {
                if (keyCode !== 13) return;
                tagOutput.value = tagOutput.value + tagInputValue + ';';
                var newTag = document.createElement('div');
                newTag.addEventListener('click', tagOnClick);
                newTag.addEventListener('mousemove', tagOnMouseMove);
                newTag.addEventListener('mouseout', tagOnMouseOut);
            }
            newTag.innerHTML = tagInputValue;
            tagList.appendChild(newTag);
        }
        if (!edit) tagInput.value = '';
    };
    this.tagOnClick = function () {
        if ((this.offsetWidth + getOffsetLeft(this) - event.pageX) < parseInt(this.style.backgroundSize, 10)) {
            tagOutput.value = tagOutput.value.replace(this.innerHTML + ';', '');
            this.parentNode.removeChild(this);
            tagInput.value = '';
            edit = false;
        } else {
            setEdit(this);
        }
        tagInput.focus();
    };
    this.tagOnMouseMove = function () {
        if ((this.offsetWidth + getOffsetLeft(this) - event.pageX) < parseInt(this.style.backgroundSize, 10)) {
            this.style.backgroundSize = '16px';
        } else {
            this.style.backgroundSize = '18px';
        }
    };
    this.tagOnMouseOut = function () {
        this.style.backgroundSize = '18px';
    };
    this.setEdit = function (tag) {
        edit = true;
        editTag = tag;
        tag.style.borderColor = '#297CCF';
        tagInput.value = tag.innerHTML;
    };
    this.changeSize = function (e) {
        if (e.type === 'focus') {
            tagList.style.maxHeight = 'none';
        } else if (e.type === 'blur' && !isHover(tagList)) {
            tagList.style.maxHeight = '40px';
        }
    };
    this.isHover = function (elem) {
        return (elem.parentElement.querySelector(':hover') === elem);
    };
    this.getOffsetLeft = function (elem) {
        var offsetLeft = 0;
        while (true) { //521px over while scrolling to the right ---> (-TABLE:521px)
            if (elem.nodeName !== 'TABLE') offsetLeft += elem.offsetLeft;
            elem = elem.parentNode;
            if (elem.nodeName === 'HTML') break;
        }
        return offsetLeft;
    };
};
function tagEditorsInit() {
    var tagEditors = [];
    var tagLists = document.getElementsByClassName('tagList');
    for (var i = 0; i < tagLists.length; i++) {
        var tagEditor = new TagEditor();
        tagEditor.tagList = tagLists.item(i); // Why isn't it working??? #1
        tagEditor.i = i;
        tagEditor.init();
        tagEditors.push(tagEditor);
    }
}
window.addEventListener('load', tagEditorsInit);

Now, I will admit that I don't use the Prototype approach everywhere. Sometimes, I write a small object that I need to use in a specific instance and I don't approach it the same way I would with a complex object. But, when writing jQuery plug-ins, or special scripts for custom applications, the Prototype approach is much easier to read, breakdown, and work with.

What does this really mean? The prototype approach defines the functions with the object type, as opposed to being defined each time an instance of the object is created. To give an example using the code above, each time you create a TagEditor, that tagEditor object defines all of those functions all over again for that specific tagEditor object. If you were able to expand or open up the individual objects, it would be like reading all of those functions defined for every single one.

With the prototype approach, it's more or less class based, like an object oriented lang like C++ or C# would have. Whenever you call the "tagOnKeyPress" function, it uses the one associated with the TagEditor type. Not the one defined with the "tagEditor" object that is of the TagEditor type. In short, the function is only defined once, and inherits access to the properties defined in the "tagEditor" object of that type.

And in case I didn't confuse you enough with that explanation, check out this MDN article which does a good job showing examples of all of this and how it differs:

Update This update is in response to your comment and Fiddle. And I just worked through your Fiddle with some modifications. Hopefully you can see what I changed. Provided below are some additional comments

1.The definition for this.tagInput needs to reference a node, not the NodeList. There are a couple spots I think you are expecting a single node but are getting a NodeList of length 1. To reference the actual DOM element to assign an EventListener, you just need to specify the 0 index of the NodeList.

2.When you bind a function as a handler, you don't want to execute the function unless you are returning a function. Bullet 3 above shows an example of this. When you add parenthesis after a function name (myMouseHandler()) it executes the function at that moment. It doesn't wait until the even occurs.

3.When you are working within a specific scope, you can define local variables to simplify working with object properties. For instance, if you use "this.tagList" much, you might tired of typing "this.tagList" everywhere. When you define it, you can assign the property and a local var like so:

var tl = this.tagList = document.getElementsByClassName('tagList').item(tagListID);

and you can then use "tl" within that scope.

this.tagOuput.value = tl.value;

4.When assigning event handlers using Prototype, you still need a way to reference the main Object you're working with. An anonymous function at the eventListener binding provides an easy way to do this where needed.

// function is the actual handler. e = event, this = element, te = tagEditor
tagInput.addEventListener('mouseout', function(e) { te.tagOnClick(e, this); });

I tried to clean up your fiddle a bit and get working what I could guess you were attempting. There is probably more you need but this might provide a clearer position for you to work from.

Fiddle: https://jsfiddle.net/938z8x98/13/

6 Comments

I wrote a new code using Prototypes: JSFiddle, but I get still an error message, because tagInput isn't defined in tagOnKeyPress.
You're still trying to call the function at the time of binding the eventHandler. Look at the difference between your tagInput.addEventListener line and mine above. Notice that I don't add the (event.keyCode) to the end? That attempts to execute the function at that moment. That's not the correct way to bind a handler. You want to pass the function. When you execute it you are attempting to pass the result of the function execution.
I updated the post with more info and a Fiddle modification from your fiddle in the comment.
Thank you very much! It's working! JSFiddle I have just 2 final questions: Why are there problems with 'tagListMouseOut'? (described in the fiddle) Should I use the original Version (Single Edition) or the Prototype Way (Multiple Edition) for sites with just one tagEditor?
On your commented line, the event listener is bound to "this.tagListMouseOut". On my line, I use an anonymous function to call tE.tagListMouseOut" where "tE" is the TagEditor object being created. The issue is Scope. In the "tagListMouseOut" function, "this" refers to the scope of the function. When it's directly called from an event trigger, the scope of "this" is the event target; or the element. With my approach, the scope of "this" is the TagEditor object.
|
1

Prototype way:

function MyClass(arg1, arg2) {
  this.prop = arg1
  this.prop2 = arg2
}

MyClass.prototype.myFcuntion = function() {
  this.prop = 'changed prop';
}
MyClass.prototype.property12 = 'some property'

var instance = new MyClass('property1', 'property2');
instance.prop; // return string 'property1'
instance.myFunction();
instance.prop; // return string 'changed prop'

4 Comments

There is a difference in the behavior between this method and the one by forgivenson. The prototype way is defined only once, and is the same method accessed for every instance of MyClass, while the encapsulated functions defined with "this.firstFunc" by forgivenson are declared for each instance of MyClass, and into memory each time a new MyClass object is created. Both have different use cases.
Yes that's true. Javascript is Prototype oriented and the prototype way is faster. @Minding 's code loops and creates much instances of tagEditor classes, so it will re-create all functions over and over again. That is slow. I think the prototype way is better here. I never yet founded an case where I need Classical OOP in javascript.
I will try to code another version using Prototypes, but I don't understand it that good and it will take some time.
I wrote a new code using Prototypes: JSFiddle, but I get still an error message, because tagInput isn't defined in tagOnKeyPress.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.