How to split a (p5.js) sketch into multiple files

edited February 2016 in p5.js

For larger sketches it would be really handy to be able to have parts of it (configuration data, for example) in a separate file. But I haven't found any way to do this. The sketch file doesn't seem to be able to successfully reference another file. I have tried using a script tag on the main html page, followed by the sketch file, but the sketch file can't seem to see it.

I'm not against learning and using something like require.js if that will solve the problem. But because of the nature of p5.js I'm not sure that would be a solution.

Answers

  • edited February 2016
    • I'm only a JS amateur and don't even know how to use modules like "require.js" yet. X_X
    • But I guess I've learned how <script></script> tags work! B-)
    • Its default behavior is to completely load & run each <script> tag before going to the other tags.
    • What's important to emphasize is that each ".js" script is 100% executed before the rest of the ".html" file continues.
    • Therefore the order in which all scripts are loaded & run can get buggy if 1 or more ".js" files depend upon others, which should happen 1st. That's what is known as dependency hell!

    Let's say we have this "sketch.js" file below:

    function setup() {
      background('blue');
      console.log(p5.SoundFile);  // check "p5.sound.js"
      console.log(createElement); // check "p5.dom.js"
    }
    
    • If "p5.dom.js" was properly loaded & merged into "p5.js", function createElement() is displayed at the console.
    • Same for class p5.SoundFile. Only if "p5.sound.js" is properly fused w/ "p5.js".
    • Let's say those 3 library files are inside subfolder "libs/".
    • Then we got this "index.html" file at the root, along w/ "sketch.js":
    <script src=libs/p5.js></script>
    <script src=libs/p5.dom.js></script>
    <script src=libs/p5.sound.js></script>
    
    <script src=sketch.js></script>
    
    • It's very important that "libs/p5.js" is loaded & run before both "libs/p5.dom.js" & "libs/p5.sound.js".
    • B/c both of them need to inject themselves into class p5.
    • Otherwise it's gonna fail and we'll see a message error in our "sketch.js".
    • Try out this version which places "libs/p5.dom.js" before "libs/p5.js":
    <script src=libs/p5.dom.js></script>
    <script src=libs/p5.js></script>
    <script src=libs/p5.sound.js></script>
    
    <script src=sketch.js></script>
    

    console.log(p5.SoundFile); works. While console.log(createElement); crashes! @-)

    This is so b/c "libs/p5.dom.js" haven't found variable p5 in the global scope and couldn't inject itself there. That is, "libs/p5.js" hasn't been loaded & run yet! >-)

  • edited February 2016

    A more sophisticated and self-contained ".html" file which loads all 3 libs directly from http://p5js.org.
    And the "sketch.js" is now merged and inlined in its own <script></script>: :ar!

    <script src=http://p5js.org/js/p5.min.js   defer></script>
    <script src=http://p5js.org/js/p5.dom.js   defer></script>
    <script src=http://p5js.org/js/p5.sound.js defer></script>
    
    <script>
    
    function setup() {
      background('blue');
      console.log(p5.SoundFile);  // check "p5.sound.js"
      console.log(createElement); // check "p5.dom.js"
    }
    
    </script>
    
  • no tabs in p5 ?

  • @Chrisir: Not everyone is using the p5 editor

    @TheProcessor: this more or less the first question I had when I rejoined the forum and led to a very useful discussion on 'Class' structure in JS. See: Splitting a project into separate files.

    In short: the standard sketch code itself should remain simple enough that you can keep it in a single file. You can easily separate out your other code (e.g. 'Classes') into separate files (for example like this). I'd personally recommend using instance mode as this doesn't pollute the global scope.

    requireJS can be made to work with p5js; but felt rather heavy going to me: lots of boilerplate code and it felt fiddly to set up... :/

    I never did get round to trying it with Browserify; but have just been playing around with setting up a web dev environment working with Node, Gulp, Babel and SASS with the intent of experimenting with Browserify, Webpack etc. and eventually setting up my own Yeoman generator; with a sub-generator to scaffold p5js and pixijs projects. At some point the project files will go on Github and I'll post a link on the forum ;)

    If you're interested in web-dev and not yet using Node based dev tools; they're well worth learning; and Yeoman is a massive help in getting started. In this case generator-dyno would be a good place to start.

  • blindfish: thanks for the generator-dyno link; I've been using most of those things individually so that will be a help. I've also been looking at your example code.

    Playing around with some example code a bit more, it seems that the problem I'm having is a circular dependency issue: the classes have references to p5.js functions, and the sketch (in instance mode) naturally has references to the class files. No matter what order I put the script tags in, I get Uncaught Reference errors. If I eliminate references to p5 functions in the class files the problem goes away, but that is a pretty big limitation to live with. Is there a way to get around this?

  • Here's a example. With everything in sketch.js it works.

    sketch.js

    var s = function(p) {
    
    var ballDef = {
        boxX: 200,
        boxY: 200,
        boxZ: -100,
        zLimit: -200,
        boxRotation: 0,
        drawing : function(size) {
            p.box(size);
        }
    }
    
    p.setup = function() {
    p.createCanvas(p.windowWidth, p.windowHeight, p.WEBGL);
    p.ortho(-p.width, p.width, p.height, -p.height, 0.1, 100);
    };
    
    p.draw = function() {
    
    p.translate(ballDef.boxX,ballDef.boxY,ballDef.boxZ);
    p.rotate(ballDef.boxRotation, [1,1,0]);
    ballDef.drawing(60);
    
    ballDef.boxRotation = ballDef.boxRotation + .1;
    ballDef.boxX = ballDef.boxX - 1;
    ballDef.boxY = ballDef.boxY - 2;
    ballDef.boxZ = ballDef.boxZ - 1;
    console.log(ballDef.boxZ);
    
      // Position Resets
      if (ballDef.boxY < -p.height) {
        ballDef.boxY = p.height;
        };
        if (ballDef.boxX < -p.width) {
        ballDef.boxX = p.width;
        };
        if (ballDef.boxZ < ballDef.zLimit) {
            ballDef.boxZ = -100;
        };
    
    };
    };
    
    var myp5 = new p5(s,'sketch0'); 
    

    and if I move the ball def into a separate file and keep the function with a p5 reference in the sketch file, it works, like so:

    ballDef.js

    var ballDef = {
        boxX: 200,
        boxY: 200,
        boxZ: -100,
        zLimit: -200,
        boxRotation: 0
    }
    

    sketch.js

    var s = function(p) {
    
    ballDef.drawing = function(size) {
      p.box(size);
    }
    
    p.setup = function() {
    p.createCanvas(p.windowWidth, p.windowHeight, p.WEBGL);
    p.ortho(-p.width, p.width, p.height, -p.height, 0.1, 100);
    };
    
    p.draw = function() {
    
    p.translate(ballDef.boxX,ballDef.boxY,ballDef.boxZ);
    p.rotate(ballDef.boxRotation, [1,1,0]);
    ballDef.drawing(60);
    
    ballDef.boxRotation = ballDef.boxRotation + .1;
    ballDef.boxX = ballDef.boxX - 1;
    ballDef.boxY = ballDef.boxY - 2;
    ballDef.boxZ = ballDef.boxZ - 1;
    console.log(ballDef.boxZ);
    
      // Position Resets
      if (ballDef.boxY < -p.height) {
        ballDef.boxY = p.height;
        };
        if (ballDef.boxX < -p.width) {
        ballDef.boxX = p.width;
        };
        if (ballDef.boxZ < ballDef.zLimit) {
            ballDef.boxZ = -100;
        };
    
    };
    };
    
    var myp5 = new p5(s,'sketch0'); 
    

    but if I move the entire ball definition including the p5-referencing function to a separate file, it fails, regardless of the order I load the script files.

  • edited February 2016
    • When some variable is declared w/ var it is scoped within its function.
    • And they can't be accessed outside the function it was declared within!
    • So your variable ballDef needs to be in the global scope instead, outside any functions or classes.
    • Unless you make it a property of some other object, whose variable is also in global scope.
    • Another option is pass the ballDef object as an extra argument for your s function.
    • However we can't do that b/c s is a callback for p5's constructor.
    • And we can't add extra arguments for it. They're already determined by p5's constructor. :(
  • I think ballDef in my example is in the global scope; the var isn't being declared inside of a function. And removing the var keyword makes no difference. Actually, putting everything in the global namespace (i.e. also removing the encapsulation of the sketch) doesn't fix the problem either.

  • edited February 2016 Answer ✓
    • All depends on the order the ".js" are processed (load & execution).
    • I was just now expanding my example about loading multiple libraries in the correct order.
    • Now I've also included multiple ".js" classes. Which all must be loaded & run before the main "sketch.js" script.
    • Inside subfolder "classes/", now there are "Animal.js", "Dog.js", "Cat.js" & "Lion.js".
    • "Animal.js" is a script which got base class Animal declared & implemented.
    • "Dog.js" got class Dog & "Cat.js" got class Cat, both extending Animal.
    • And lastly "Lion.js", which got class Lion. Which depends on Cat and Animal
    • In that configuration, file "Animal.js" must happen before all the other 3. B/c they all depend on it.
    • Direct descendants "Dog.js" & "Cat.js" can be run in any order after "Animal.js".
    • For they don't depend on each other, but only on "Animal.js".
    • But "Lion.js" depends on "Cat.js". So "Cat.js" must happen before the former.
    • And not surprisingly, "sketch.js" needs to be the last of all the whole batch.
    • B/c "sketch.js" depends on everything else. Both the 3 libraries in "libs/" & the 4 classes in "classes/".
    • Here's the current correct ".html" file now. Changing the order of the scripts can affect the current 100% success.
    • Of course not all changes result in fail. For example, given the 4 ".js" files in "classes/" don't depend on any of the 3 ".js" in "libs/", they can be loaded before the latter.
    • Same for loading "Cat.js" before "Dog.js". But not "Lion.js" before "Cat.js" nor any of those 3 before "Animal.js". =P~

    "index.html":

    <script defer src=libs/p5.js></script>
    <script defer src=libs/p5.dom.js></script>
    <script defer src=libs/p5.sound.js></script>
    
    <script defer src=classes/Animal.js></script>
    <script defer src=classes/Dog.js></script>
    <script defer src=classes/Cat.js></script>
    <script defer src=classes/Lion.js></script>
    
    <script defer src=sketch.js></script>
    

    "sketch.js":

    function setup() {
      "use strict"; "use strong"
    
      background('blue')
    
      console.log(p5.SoundFile)  // check "p5.sound.js"
      console.log(createElement) // check "p5.dom.js"
      console.log('\n')
      
      new Animal('GoToLoop').speak() // GoToLoop makes some noise...
      new Dog('Spark').speak()       // Spark barks!
      new Cat('Fifi').speak()        // Fifi meows!
      new Lion('King').speak()       // King meows! ... and then ROARS!!!
    }
    

    "classes/Animal.js":

    "use strict"; "use strong"
    
    class Animal {
      constructor (name) {
        this.name = name
      }
    
      speak() {
        console.log(`${this.name} makes some noise...`)
      }
    }
    

    "classes/Dog.js":

    "use strict"; "use strong"
    
    class Dog extends Animal {
      speak() {
        console.log(`${this.name} barks!`)
      }
    }
    

    "classes/Cat.js":

    "use strict"; "use strong"
    
    class Cat extends Animal {
      speak() {
        console.log(`${this.name} meows!`)
      }
    }
    

    "classes/Lion.js":

    "use strict"; "use strong"
    
    class Lion extends Cat {
      speak() {
        super.speak()
        console.info('... and then ROARS!!!')
      }
    }
    
  • edited February 2016

    Ah! And for those who rather prefer 1 unified & self-sufficient ".html" file, which grabs all libraries remotely, I present ye this: :-h

    <script defer src=http://p5js.org/js/p5.min.js></script>
    <script defer src=http://p5js.org/js/p5.dom.js></script>
    <script defer src=http://p5js.org/js/p5.sound.js></script>
    
    <script>
    "use strict"; "use strong"
    
    class Animal {
      constructor (name) {
        this.name = name
      }
    
      speak() {
        console.log(`${this.name} makes some noise...`)
      }
    }
    </script>
    
    <script>
    "use strict"; "use strong"
    
    class Dog extends Animal {
      speak() {
        console.log(`${this.name} barks!`)
      }
    }
    </script>
    
    <script>
    "use strict"; "use strong"
    
    class Cat extends Animal {
      speak() {
        console.log(`${this.name} meows!`)
      }
    }
    </script>
    
    <script>
    "use strict"; "use strong"
    
    class Lion extends Cat {
      speak() {
        super.speak()
        console.info('... and then ROARS!!!')
      }
    }
    </script>
    
    <script>
    function setup() {
      "use strict"; "use strong"
    
      background('blue')
    
      console.log(p5.SoundFile)  // check "p5.sound.js"
      console.log(createElement) // check "p5.dom.js"
      console.log('\n')
      
      new Animal('GoToLoop').speak() // GoToLoop makes some noise...
      new Dog('Spark').speak()       // Spark barks!
      new Cat('Fifi').speak()        // Fifi meows!
      new Lion('King').speak()       // King meows! ... and then ROARS!!!
    }
    </script>
    
  • OK goToLoop, I tried your example locally, and then went back and looked at my code. You are correct: putting the sketch and the class file in the global space does work. Not sure exactly what I was doing wrong before when I tried that. Working code:

    index.html

    <html> 
    <head>
      <meta charset="UTF-8">
      <script src="libraries/p5.js"></script>
      <script src="ballDef.js"></script>
      <script src="sketch.js"></script>
      <style> body {padding: 0; margin: 0;} </style>
    </head>
    
    <body>
    </body>
    </html>
    

    ballDef.js

    ballDef = {
        boxX :  200,
        boxY :  200,
        boxZ :  -100,
        zLimit : -200,
        boxRotation : 0,
        drawing : function(size) {
            box(size);
        }
    }
    

    sketch.js

    setup = function() {
    createCanvas(windowWidth, windowHeight, WEBGL);
    ortho(-width, width, height, -height, 0.1, 100);
    };
    
    draw = function() {
    
    translate(ballDef.boxX,ballDef.boxY,ballDef.boxZ);
    rotate(ballDef.boxRotation, [1,1,0]);
    ballDef.drawing(60);
    
    ballDef.boxRotation = ballDef.boxRotation + .1;
    ballDef.boxX = ballDef.boxX - 1;
    ballDef.boxY = ballDef.boxY - 2;
    ballDef.boxZ = ballDef.boxZ - 1;
    console.log(ballDef.boxZ);
    
      // Position Resets
      if (ballDef.boxY < -height) {
        ballDef.boxY = height;
        };
        if (ballDef.boxX < -width) {
        ballDef.boxX = width;
        };
        if (ballDef.boxZ < ballDef.zLimit) {
            ballDef.boxZ = -100;
        };
    
    };
    

    Thank you very much for your latest example!

    I'm still curious if it's possible to do separate class files without putting the sketch in the global namespace.

  • edited February 2016

    ... if it's possible to do separate class files without putting the sketch in the global namespace.

    • It is very so! But it's much more elaborate to pull that out! >-)
    • We've all heard about how evil & heretic it is to pollute the global scope. >:)
    • But not as much of us had searched the wherefores for it. I-)
    • The actual problem arises when all libraries we load & run decides to dump their whole into the global namespace.
    • In such scenario, the odds would be very high for some variable names from 1 library to clash with the others. :@)
    • So once upon a time it was decided that none should do that as some kinda gentleman agreement.
    • However 100% avoidance of global pollution isn't possible. What libraries do is try to minimize that. :-j
    • For example, JQuery pollutes the $ variable. While Underscore grabs variable _ for itself.
    • And p5.js, when it's in "instance" mode, just claims variable p5 as its constructor.
    • As we can notice so far, the idea is to taint the minimal necessary rather than avoid 100%. *-:)
    • But the irony is as long as only 1 library dares to dirty the global scope, no real damage is possible. :P
    • That's why we can use p5.js in its sinful "global" mode w/o any problems. Even when mixing it up w/ other libs! =P~
    • Nonetheless it's still a must to learn how to do it in the cleanest way for good appearances. ;))
    • I've just converted my previous example to use only 1 global namespace variable: classes.
    • Therefore just 2 variables are added to the global scope: p5 + classes! \m/
  • edited February 2016

    1st step is to declare variable classes in each ".js" file that needs it, in order to make sure it exists regardless the order they were loaded & run: var classes.

    Next we wrap the once "global" structure body inside an enclosed auto-run anonymous function, having 1 parameter which will carry on the classes reference:

    (c => {
      // structure body
    })()
    

    In the ending parens pair there, we pass our namespace classes, in a way that it auto-initializes w/ {} in case it's still undefined or null: })(classes || (classes = {}))

    And just before })(classes || (classes = {})), inside the wrapped body, we annex the class or function constructor to the passed classes reference inside the enclosing function:

      // Annex the class Animal to parameter c,
      // which in turn represents the passed classes namespace object:  
      c.Animal = Animal 
    })(classes || (classes = {}))
    

    Now the final result for the 4 "classes/*.js" files; enjoy: :-bd

    "classes/Animal.js":

    var classes
    (c => {
      "use strict"; "use strong"
    
      class Animal {
        constructor (sketch, name) {
          this.p = sketch
          this.name = name
        }
    
        speak() {
          console.log(`${this.name} makes some noise...`)
        }
    
        display(x, y, w, h, c) {
          this.p.fill(c)
          this.p.rect(x, y, w, h)
          return this
        }
      }
    
      c.Animal = Animal
    })(classes || (classes = {}))
    

    "classes/Dog.js":

    var classes
    (c => {
      "use strict"; "use strong"
    
      const Animal = c.Animal // import
    
      class Dog extends Animal {
        speak() {
          console.log(`${this.name} barks!`)
        }
    
        display() {
          const x = this.p.width>>1, y = 0, w = x, h = this.p.height>>1
          return super.display(x, y, w, h, 'red')
        }
      }
    
      c.Dog = Dog
    })(classes || (classes = {}))
    

    "classes/Cat.js":

    var classes
    (c => {
      "use strict"
    
      const Animal = c.Animal // import
    
      class Cat extends Animal {
        speak() {
          console.log(`${this.name} meows!`)
        }
    
        display() {
          if (arguments.length)  return super.display(...arguments)
    
          const x = 0, y = this.p.height>>1, w = this.p.width>>1, h = y
          return super.display(x, y, w, h, 'green')
        }
      }
    
      c.Cat = Cat
    })(classes || (classes = {}))
    

    "classes/Lion.js":

    var classes
    (c => {
      "use strict"; "use strong"
    
      const Cat = c.Cat // import
    
      class Lion extends Cat {
        speak() {
          super.speak()
          console.info('... and then ROARS!!!')
        }
    
        display() {
          const x = this.p.width>>1, y = this.p.height>>1, w = x, h = y
          return super.display(x, y, w, h, 'blue')
        }
      }
    
      c.Lion = Lion
    })(classes || (classes = {}))
    
  • edited February 2016

    Now here's the "sketch.js" file which uses new for the p5's constructor and passes our sketch as a callback parameter for it: :bz

    "sketch.js":

    new p5(p => {
      "use strict"; "use strong"
    
      const Animal = classes.Animal, // imports
            Dog = classes.Dog,
            Cat = classes.Cat,
            Lion = classes.Lion
    
      p.setup = () => {
        p.createCanvas(400, 400)
        p.smooth().rectMode(p.CORNER).strokeWeight(4).stroke('orange')
    
        console.log(p5.SoundFile)    // check "p5.sound.js"
        console.log(p.createElement) // check "p5.dom.js"
        console.log('\n')
    
        new Animal(p, 'GoToLoop')
              .display(0, 0, p.width>>1, p.height>>1, 'black').speak()
    
        new Dog(p, 'Spark').display().speak()
        new Cat(p, 'Fifi').display().speak()
        new Lion(p, 'King').display().speak()
      }
    })
    

    Not even the expression new p5(p => { }) is assigned to a variable.
    And the whole main sketch lambda is initialized inline as an argument for it.
    Therefore no extra global scope pollution! Just variables p5 & classes and nothing more!!! :))

    And lastly, our ".html" file. Where everything is loaded & run in the correct order: B-)

    "index.html":

    <script defer src=libs/p5.js></script>
    <script defer src=libs/p5.dom.js></script>
    <script defer src=libs/p5.sound.js></script>
    
    <script defer src=classes/Animal.js></script>
    <script defer src=classes/Dog.js></script>
    <script defer src=classes/Cat.js></script>
    <script defer src=classes/Lion.js></script>
    
    <script defer src=sketch.js></script>
    
  • edited February 2016

    And the self-sufficient & unified "index.html" file too: :(|)

    <script src=http://p5js.org/js/p5.min.js></script>
    <script src=http://p5js.org/js/p5.dom.js></script>
    <script src=http://p5js.org/js/p5.sound.js></script>
    
    <script>
    
    var classes
    (c => {
      "use strict"; "use strong"
    
      class Animal {
        constructor (sketch, name) {
          this.p = sketch
          this.name = name
        }
    
        speak() {
          console.log(`${this.name} makes some noise...`)
        }
    
        display(x, y, w, h, c) {
          this.p.fill(c)
          this.p.rect(x, y, w, h)
          return this
        }
      }
    
      c.Animal = Animal
    })(classes || (classes = {}))
    
    </script>
    
    <script>
    
    var classes
    (c => {
      "use strict"; "use strong"
    
      class Dog extends c.Animal {
        speak() {
          console.log(`${this.name} barks!`)
        }
    
        display() {
          const x = this.p.width>>1, y = 0, w = x, h = this.p.height>>1
          return super.display(x, y, w, h, 'red')
        }
      }
    
      c.Dog = Dog
    })(classes || (classes = {}))
    
    </script>
    
    <script>
    
    var classes
    (c => {
      "use strict"; "use strong"
    
      class Cat extends c.Animal {
        speak() {
          console.log(`${this.name} meows!`)
        }
    
        display(...args) {
          if (args.length)  return super.display(...args)
    
          const x = 0, y = this.p.height>>1, w = this.p.width>>1, h = y
          return super.display(x, y, w, h, 'green')
        }
      }
    
      c.Cat = Cat
    })(classes || (classes = {}))
    
    </script>
    
    <script>
    
    var classes
    (c => {
      "use strict"; "use strong"
    
      class Lion extends c.Cat {
        speak() {
          super.speak()
          console.info('... and then ROARS!!!')
        }
    
        display() {
          const x = this.p.width>>1, y = this.p.height>>1, w = x, h = y
          return super.display(x, y, w, h, 'blue')
        }
      }
    
      c.Lion = Lion
    })(classes || (classes = {}))
    
    </script>
    
    <script>
    
    new p5(p => {
      "use strict"; "use strong"
    
      const Animal = classes.Animal, // imports
            Dog = classes.Dog,
            Cat = classes.Cat,
            Lion = classes.Lion
    
      p.setup = () => {
        p.createCanvas(400, 400)
        p.smooth().rectMode(p.CORNER).strokeWeight(4).stroke('orange')
    
        console.log(p5.SoundFile)    // check "p5.sound.js"
        console.log(p.createElement) // check "p5.dom.js"
        console.log('\n')
    
        new Animal(p, 'GoToLoop')
              .display(0, 0, p.width>>1, p.height>>1, 'black').speak()
    
        new Dog(p, 'Spark').display().speak()
        new Cat(p, 'Fifi').display().speak()
        new Lion(p, 'King').display().speak()
      }
    })
    
    </script>
    
  • Very nice in-depth explanation. I was able to use the pattern successfully by rewriting my ballDef object as a Class. The (c => { }) syntax is new to me though. I tried re-writing it in standard function syntax but without success. Is the => operator vital to making this work?

  • edited February 2016

    ... by rewriting my ballDef object as a Class.

    Your ballDef, as I'm understanding your code, and except its drawing(), was acting more like a C struct.
    Since it's got a method, it should indeed become a full class, w/ capitalized B and 1 constructor: $-)

    "classes/Ball.js":

    var classes
    (c => {
      "use strict"
    
      class Ball {
        constructor (p, x, y, z, diam, lim, rot) {
          this.p = p
          this.x = x, this.y = y, this.z = z
          this.diam = diam, this.lim = lim, this.rot = rot
        }
    
        drawing() {
          this.p.translate(this.x, this.y, this.z)
          this.p.rotate(this.rot)
          this.p.box(this.diam)
        }
      }
    
      c.Ball = Ball
    })(classes || (classes = {}))
    

    The (c => { }) syntax is new to me though.

    That, plus class and many more, is ECMA6 (a.K.a ES2015) JS. ;))
    (c => {})(classes || (classes = {})) is almost the same functionality as the old:
    (function (c) {})(classes || (classes = {}))
    If you still prefer, you can continue using the old way. :-h

  • edited February 2016

    Ah yes, ECMA2105...I've done class inheritance before via 'call', like so:

    function Thing(args) {
        // (more stuff)
    }
    
    function Label(args) {
        Thing.call(this, args); //extends Thing
        // (more stuff)
    }
    Label.prototype = Object.create(Thing.prototype);
    

    but 'class' and 'extends' is more explicit and less verbose, which is a good thing.

  • Just wanted to add for anyone reading this discussion, that if you want to add properties directly to a subclass (and not just methods, as in the previous examples) here's a way to do it- add a constructor to the subclass, and have that constructor call super(). Here's an example, using the Animal class from above as the parent class:

    var classes
    (c => {
      "use strict"; "use strong"
    
      const Animal = c.Animal // import
    
      class Dog extends Animal {
        constructor(sketch, name) {
          super(sketch, name)
          this.genus = 'canine'
        }
        speak() {
          console.log(`${this.name} barks!`)
          console.log('Because it is a ' + this.genus)
        }
    
        display() {
          const x = this.p.width>>1, y = 0, w = x, h = this.p.height>>1
          return super.display(x, y, w, h, 'red')
        }
      }
    
      c.Dog = Dog
    })(classes || (classes = {}))
    
  • My experiments with Browserify are proving rather instructive. I'm already regretting my decision not to pursue it more thoroughly in the past... This probably won't come as a surprise to anyone already familiar with Browserify; but here's what it brings to the mix:

    1. Vastly simplifies resolution of scoping issues
    2. Reduces boilerplate code usually required to achieve this
    3. Simplifies dependency management
    4. Reduces the requirement for outdated/bloated Class structures (Modules FTW!)
    5. Can work with external libraries like p5js; either by explicitly requiring them as a dependency (untested: may need application of a shim) or by creating a global reference: read on...

    I also had a face-palm moment when thinking about use of p5js with VanillaJS. Using instance mode you just need to assign the p5 instance you create to a truly global variable (by attaching to window). You then don't need to pass references around your 'classes' (assuming that these will be used only after p5 has instantiated):

    "use strict";
    
    // Attaching p5 to window as a global var.
    // (Apparently no self-respecting JS library would be without a $)
    // Note that it's not wise to overwrite the 'p5' var as you 
    // may still need to reference the raw library...
    // There are other, lazier, ways to attach to window
    // but this makes it clear that it was an explicit decision
    // and conforms to "use strict";
    window.P$ = new p5(function (p) {
    
        var c;
    
        p.setup = function () {
            p.createCanvas(600, 400);
    
            c = new Circle();
        };
    
        p.draw = function () {
            p.background('#c10');
            c.draw();
        };
    
    }, "sketch01");
    
    
    
    // pseudo-class
    var Circle = function() {
        console.log("circle instantiated");
    };
    
    Circle.prototype.draw = function() {
        // we can reference the global P$ to get at our p5 instance...
        P$.ellipse(P$.width/2, P$.height/2, P$.height * 0.75, P$.height * 0.75);
    }
    
  • Very nice blindfish! Using your idea and GoToLoop's syntax I made a version splitting the circle class into its own file.

    sketch.js

    "use strict"
    // Attaching p5 to window as a global var.
    // (Apparently no self-respecting JS library would be without a $)
    // Note that it's not wise to overwrite the 'p5' var as you
    // may still need to reference the raw library...
    // There are other, lazier, ways to attach to window
    // but this makes it clear that it was an explicit decision
    // and conforms to "use strict";
    
    window.P$ = new p5(p => {
      let c
    
      p.setup = function () {
        p.createCanvas(600, 400)
        c = new Circle()
        c.log()
      }
    
      p.draw = function () {
        p.background('#c10')
        c.draw()
      }
    }, "sketch01")
    

    classes/Circle.js

    "use strict"
    
    class Circle {
      constructor() {
        this.logtext = "circle instantiated"
      }
      draw() {
        P$.ellipse(P$.width/2, P$.height/2, P$.height * 0.75, P$.height * 0.75)
      }
      log() {
        console.log(this.logtext)
      }
    }
    

    index.html

    <html>
    <head>
      <meta charset="UTF-8">
      <script src="libraries/p5.js"></script>
      <!-- uncomment lines below to include extra p5 libraries -->
      <!--<script language="javascript" src="libraries/p5.dom.js"></script>-->
      <!--<script language="javascript" src="libraries/p5.sound.js"></script>-->
      <script src="sketch.js"></script>
      <script src="classes/Circle.js"></script>
      <!-- this line removes any default padding and style. you might only need one of these values set. -->
      <style> body {padding: 0; margin: 0;} </style>
    </head>
    <body>
    </body>
    </html>
    
  • is this still the simplest way to subdivide a p5.js sketch in order to have its classes in different files ?

    I'm not a native english speaker but that looks like the definition of "cumbersome " ;)

  • edited May 2018

    @phoebus, this whole discussion was about having multiple files w/o "polluting" the global scope. ;;)
    Or at least, to pollute as few as possible. 3:-O

    I don't care any of this! I put everything in global scope. Like this multi-file sketch for example: >:)
    https://Bl.ocks.org/GoSubRoutine/f4e383cfc4de8b063df3ee1c3fc66419

  • @GoToLoop cool !

    about the "use strict" though, does it only push me to make my code more rigorous or will it limit my possibilities ?

Sign In or Register to comment.