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

Community
  • 1
  • 1
Minding
  • 1,383
  • 1
  • 17
  • 29
  • See [Javascript: Do I need to put this.var for every variable in an object?](http://stackoverflow.com/q/13418669/1048572) Also, you [should not use `new function(){…}` to instantiate objects](http://stackoverflow.com/q/10406552/1048572)! – Bergi Apr 16 '15 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. – Daved Apr 16 '15 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_ – Minding Apr 16 '15 at 17:18
  • @Bergi Deleting 'this' and 'new' from the function throws this error message: _Uncaught ReferenceError: tagOnKeyPress is not defined_ – Minding Apr 16 '15 at 17:21
  • Yes but querySelectorAll returns a NodeList. Change it to tagInput = tagList.querySelectorAll('[name=tagInput]')[0]; if you want the first node. – Daved Apr 16 '15 at 17:23
  • @Minding: I did not say you should delete `this` - the linked post only explains the difference and why you have to use it here. And you should not delete `new` either, but you should move your "class" from a function expression right behind the `new` to a static declaration, and invoke it with `tagEditors[i] = new TagEditor()` – Bergi Apr 16 '15 at 17:24
  • @Daved Tried it, it effects the same as '.item(0)' and throws the same error message. So this part of the code should be fine. – Minding Apr 16 '15 at 17:26
  • @Bergi Sorry, but I don't understand what you meen (I'm from Germany, 14 years old, still at school), could you create a JSFiddle with an example? – Minding Apr 16 '15 at 17:38

3 Answers3

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

forgivenson
  • 4,394
  • 2
  • 19
  • 28
  • Error: undefined is not a function :/ – Minding Apr 16 '15 at 16:07
  • @Minding What line is that occurring on? – forgivenson Apr 16 '15 at 16:28
  • Sorry, but my code is still not working :( - Check out my JSFiddle – Minding Apr 16 '15 at 16:54
  • @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? – forgivenson Apr 16 '15 at 16:57
  • 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. – Minding Apr 16 '15 at 17:05
  • Sorry, the JS-Code didn't load - I updated the JSFiddle: https://jsfiddle.net/938z8x98/5/ – Minding Apr 16 '15 at 17:09
  • I commented on the OP. That specific error is because of tagInput being a NodeList. – Daved Apr 16 '15 at 17:18
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'
Ifch0o1
  • 900
  • 1
  • 14
  • 32
  • 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. – Daved Apr 16 '15 at 16:43
  • 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. – Ifch0o1 Apr 16 '15 at 17:13
  • I will try to code another version using Prototypes, but I don't understand it that good and it will take some time. – Minding Apr 16 '15 at 17:47
  • I wrote a new code using Prototypes: [JSFiddle](https://jsfiddle.net/938z8x98/8/), but I get still an error message, because tagInput isn't defined in tagOnKeyPress. – Minding Apr 18 '15 at 09:58
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/

Daved
  • 2,082
  • 1
  • 18
  • 23
  • I wrote a new code using Prototypes: [JSFiddle](https://jsfiddle.net/938z8x98/8/), but I get still an error message, because tagInput isn't defined in tagOnKeyPress. – Minding Apr 17 '15 at 16:25
  • 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. – Daved Apr 20 '15 at 15:09
  • I updated the post with more info and a Fiddle modification from your fiddle in the comment. – Daved Apr 20 '15 at 19:35
  • **Thank you** very much! It's working! **[JSFiddle](https://jsfiddle.net/938z8x98/14/)** I have just 2 final questions: Why are there problems with 'tagListMouseOut'? (described in the fiddle) Should I use the original Version ([Single Edition](https://jsfiddle.net/938z8x98/15/)) or the Prototype Way ([Multiple Edition](https://jsfiddle.net/938z8x98/14/)) for sites with just one tagEditor? – Minding Apr 22 '15 at 13:42
  • 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. – Daved Apr 22 '15 at 14:32
  • To that point, if you put a debugger break in the "tagListMouseOut" function, you will see an undefined error on the first conditional when binding the function directly because "this" - or the element triggering the event - does not have a property called "tagList". – Daved Apr 22 '15 at 14:34