0

In c# for example I can extend a framework type and assign an expression to my type and it will retain all the members of the base type but also have my extended methods.

I'm trying to inherit Sentence from Range.

Note the suggested duplicate doesn't apply here because Range is not susceptible to these techniques.

How can I achieve this in javascript? I have:

/* Namespace */
var WebPageReader = WebPageReader || {};

/* Classes */
WebPageReader.Sentence = function(range) {
    this.setStart(range.startContainer, range.startOffset); // bombs here
    this.setEnd(range.endContainer, range.endOffset);  
};

function test(){
  WebPageReader.Sentence.prototype = new Range();
  var dom = new WebPageReader.Dom();
  var s = new WebPageReader.Sentence(dom.GetSelectedRange());
  alert(s.endOffset)  ;
}
WebPageReader.Dom = function() {

  /* public methods */
  this.GetSelectedRange = function() {
    var sel;
    if (window.getSelection) {
      sel = window.getSelection();
      if ((sel.type == "Range" || allowCaretSelection) && sel.rangeCount) {
        return sel.getRangeAt(0);
      }
    }
    return null;
  }
}

This gives the error:

Uncaught TypeError: Illegal invocation

Attempt #2

When i change my code to this:

/* Namespace */
var WebPageReader = WebPageReader || {};

/* Classes */
WebPageReader.Sentence = function(range) {
  Range.call(this);
};

function test(){
  WebPageReader.Sentence.prototype = new Range();
  var dom = new WebPageReader.Dom();
  var s = new WebPageReader.Sentence(dom.GetSelectedRange());
  alert(s.endOffset)  ;
}

I get this error

Uncaught TypeError: Failed to construct 'Range': Please use the 'new' operator, this DOM object constructor cannot be called as a function.

toddmo
  • 20,682
  • 14
  • 97
  • 107
  • Have you tried `extends`, like `class Sentence extends Range`? – ssube Sep 12 '16 at 20:44
  • 1
    Possible duplicate of [JavaScript Inheritance](http://stackoverflow.com/questions/7486825/javascript-inheritance) – Tamas Hegedus Sep 12 '16 at 20:46
  • @TamasHegedus, it's not working with `Range` as a base class. `Range` isn't playing well with others. – toddmo Sep 12 '16 at 20:54
  • Where us Range declared? – Scott Fanetti Sep 12 '16 at 20:57
  • @ScottFanetti, you know, I don't know! It's part of javascript. In the dom. https://developer.mozilla.org/en-US/docs/Web/API/Range – toddmo Sep 12 '16 at 20:58
  • @toddmo I see, thats unfortunate. I am afraid you cannot extend that class in this case, as you will never be able to call it's superclass constructor. If the only thing you want is to add some "extension methods" to the Range class, you could take a look at the bind operator: `foo.call(range, bar)` can be written as `range::foo(bar)`. – Tamas Hegedus Sep 12 '16 at 20:59
  • Well I am an idiot. I have never had to use it - never knew it was there :( – Scott Fanetti Sep 12 '16 at 21:00
  • @ScottFanetti, you're not an idiot! My goodness, there's a trillion javascript objects ;) – toddmo Sep 12 '16 at 21:02
  • @TamasHegedus, thank you so much. If you feel like turning that into an answer, we can close this puppy! I'll look up `bind` and try to get it working! – toddmo Sep 12 '16 at 21:04
  • 1
    @TamasHegedus, still getting illegal invocation. Which reminds me of the time I got in trouble for leading a prayer group on school grounds. – toddmo Sep 12 '16 at 21:15
  • @toddmo Why are you subclassing Range? I don't see any extended functionality there, you could just use regular Ranges. – Tamas Hegedus Sep 12 '16 at 21:19
  • @TamasHegedus, regular ranges don't have ability to work on the sentence level. It would be nice to have a "range 2.0" that understands sentence navigation. But currently I'm just using containment so as to not get stuck with no progress. – toddmo Sep 12 '16 at 21:29

2 Answers2

1

You are not subclassing properly. You should use ES6 classes.

var WebPageReader = {
  Dom: function() {
    this.GetSelectedRange = function() {
      if (!window.getSelection) return null;
      return window.getSelection().getRangeAt(0);
    }
  },
  Sentence: class extends Range {
    constructor(range) {
      super();
      this.setStart(range.startContainer, range.startOffset);
      this.setEnd(range.endContainer, range.endOffset);
    }
  }
};
document.body.addEventListener('click', function() {
  var dom = new WebPageReader.Dom();
  var s = new WebPageReader.Sentence(dom.GetSelectedRange());
  console.log(s.endOffset);
});
Select text

It works because super() initializes the instance as a real Range. In ES5 you can only do things like making the instance inherit from Range.prototype, which is not enough to be a real Range instance. And Range methods can only be called on real Range instances.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • I'm putting my app on google store as a chrome extension. All chrome users will jump on it b/c it's so darn cool (lol). What percent do you think doesn't have version 42 yet (the version class was introduced)? Nice a** answer btw! – toddmo Sep 12 '16 at 21:31
  • @toddmo Chrome has forced updates, so I guess it will work for most Chrome users. See http://gs.statcounter.com/ for some stats. – Oriol Sep 12 '16 at 21:40
  • 96% according to your data. Sweet! I'll try it! – toddmo Sep 12 '16 at 21:54
1

Builtin JavaScript classes such as Error, Array or Range cannot be subclassed using only ES5 features. In browsers that support ES6 classes extending from Range might work, but this is a fragile thing yet, and I dot't feel doing so is justified in this case. I think you only need helper methods, not subclassing. You could achive similar looks by using the function bind syntax operator:

// Would have been written
Sentence.prototype.additionalOperation = function() {
    return this.endContainer ...;
}

// can write this:
function additionalOperation() {
    return this.endContainer ...
}

// usage
// instead of:
new Sentence(range).additionalOperation();
// use this:
additionalOperation.call(range);
// or with bind syntax:
range::additionalOperation();
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97