Ruthie  Bugala

Ruthie Bugala

1651204800

Nbind: Magical Headers That Make Your C++ Library Accessible From JS

nbind is a set of headers that make your C++11 library accessible from JavaScript. With a single #include statement, your C++ compiler generates the necessary bindings without any additional tools. Your library is then usable as a Node.js addon or, if compiled to asm.js with Emscripten, directly in web pages without any plugins.

nbind works with the autogypi dependency management tool, which sets up node-gyp to compile your library without needing any configuration (other than listing your source code file names).

nbind is MIT licensed and based on templates and macros inspired by embind.

Quick start

C++ everywhere in 5 easy steps using Node.js, nbind and autogypi:

Starting pointStep 1 - bindStep 2 - prepare
Original C++ code hello.cc:
#include <string> #include <iostream>   struct Greeter {  static void sayHello(    std::string name  ) {    std::cout      << "Hello, "      << name << "!\n";  } };
List your classes and methods:
// Your original code here   // Add these below it:   #include "nbind/nbind.h"   NBIND_CLASS(Greeter) {  method(sayHello); }
Add scripts to package.json:
{  "scripts": {    "autogypi": "autogypi",    "node-gyp": "node-gyp",    "emcc-path": "emcc-path",    "copyasm": "copyasm",    "ndts": "ndts"  } }
Step 3 - installStep 4 - buildStep 5 - use!
Run on the command line:
npm install --save \  nbind autogypi node-gyp   npm run -- autogypi \  --init-gyp \  -p nbind -s hello.cc
Compile to native binary:
npm run -- node-gyp \  configure buildOr to Asm.js:
npm run -- node-gyp \  configure build \  --asmjs=1
Call from Node.js:
var nbind = require('nbind'); var lib = nbind.init().lib;   lib.Greeter.sayHello('you');Or from a web browser (see below).

The above is all of the required code. Just copy and paste in the mentioned files and prompts or take a shortcut:

git clone https://github.com/charto/nbind-example-minimal.git
cd nbind-example-minimal
npm install && npm test

See it run!

(Note: nbind-example-universal is a better starting point for development)

Requirements

You need:

And one of the following C++ compilers:

  • GCC 4.8 or above.
  • Clang 3.6 or above.
  • Emscripten 1.35.0 or above.
  • Visual Studio 2015 (the Community version is fine).

Features

nbind allows you to:

  • Use your C++ API from JavaScript without any extra effort.
    • From Node.js, Electron and web browsers (using asm.js on Chrome, Firefox and Edge).
    • On Linux, OS X and Windows.
    • Without changes to your C++ code. Simply add a separate short description at the end.
  • Distribute both native code and an asm.js fallback binary.
  • Automatically generate TypeScript .d.ts definition files from C++ code for IDE autocompletion and compile-time checks of JavaScript side code.

In more detail:

  • Export multiple C++ classes, even ones not visible from other files.
  • Export C++ methods simply by mentioning their names.
  • Auto-detect argument and return types from C++ declarations.
  • Automatically convert types and data structures between languages.
  • Call C++ methods from JavaScript with type checking.
  • Pass JavaScript callbacks to C++ and call them with any types.
  • Pass instances of compatible classes by value between languages (through the C++ stack).

The goal is to provide a stable API for binding C++ to JavaScript. All internals related to JavaScript engines are hidden away, and a single API already supports extremely different platforms.

Works on your platform

TargetDevelopment platform
 Linux / OS XWindows
NativeBuild status Build status 
Asm.jsBuild status Tested manually

Roadmap

More is coming! Work is ongoing to:

  • Precompile to a single native library for all versions Node.js and Electron on the same platform
    • Precompiled addons for different Node.js versions for efficiently calling the library will be provided with nbind
  • Support native Android and iPhone apps.

Future 0.x.y versions should remain completely backwards-compatible between matching x and otherwise with minor changes. Breaking changes will be listed in release notes of versions where y equals 0.

Contributing

Please report issues through Github and mention the platform you're targeting (Node.js, asm.js, Electron or something else). Pull requests are very welcome.

Warning: rebase is used within develop and feature branches (but not master).

When developing new features, writing tests first works best. If possible, please try to get them working on both Node.js and asm.js. Otherwise your pull request will get merged to Master only after maintainer(s) have fixed the other platform.

Installing Emscripten to develop for asm.js can be tricky. It will require Python 2.7 and setting paths correctly, please refer to Emscripten documentation. The bin/emcc script in this package is just a wrapper, the actual emcc compiler binary should be in your path.

You can rebuild the asm.js library and run tests as follows:

npm run clean-asm && npm run prepublish && npm run test-asm

User guide

Installing the examples

nbind examples shown in this user guide are also available to download for easier testing as follows:

Extract this zip package or run:

git clone https://github.com/charto/nbind-examples.git

Enter the examples directory and install:

cd nbind-examples
npm install

Creating your project

Once you have all requirements installed, run:

npm init
npm install --save nbind autogypi node-gyp

nbind, autogypi and node-gyp are all needed to compile a native Node.js addon from source when installing it. If you only distribute an asm.js version, you can use --save-dev instead of --save because users won't need to compile it.

Next, to run commands without installing them globally, it's practical to add them in the scripts section of your package.json that npm init just generated. Let's add an install script as well:

  "scripts": {
    "autogypi": "autogypi",
    "node-gyp": "node-gyp",
    "emcc-path": "emcc-path",
    "copyasm": "copyasm",

    "install": "autogypi && node-gyp configure build"
  }

emcc-path is needed internally by nbind when compiling for asm.js. It fixes some command line options that node-gypi generates on OS X and the Emscripten compiler doesn't like. You can leave it out if only compiling native addons.

The install script runs when anyone installs your package. It calls autogypi and then uses node-gyp to compile a native addon.

autogypi uses npm package information to set correct include paths for C/C++ compilers. It's needed when distributing addons on npm so the compiler can find header files from the nbind and nan packages installed on the user's machine. Initialize it like this:

npm run -- autogypi --init-gyp -p nbind -s hello.cc

Replace hello.cc with the name of your C++ source file. You can add multiple -s options, one for each source file.

The -p nbind means the C++ code uses nbind. Multiple -p options can be added to add any other packages compatible with autogypi.

The --init-gyp command generates files binding.gyp and autogypi.json that you should distribute with your package, so that autogypi and node-gyp will know what to do when the install script runs.

Now you're ready to start writing code and compiling.

Configuration

Refer to autogypi documentation to set up dependencies of your package, and how other packages should include it if it's a library usable directly from C++.

--asmjs=1 is the only existing configuration option for nbind itself. You pass it to node-gyp by calling it like node-gyp configure build --asmjs=1. It compiles your package using Emscripten instead of your default C++ compiler and produces asm.js output.

Calling from Node.js

First nbind needs to be initialized by calling nbind.init which takes the following optional arguments:

  • Base path under which to look for compiled binaries. Default is process.cwd() and __dirname is a good alternative.
  • Binary code exports object. Any classes from C++ API exported using nbind will be added as members. Default is an empty object. Any existing options will be seen by asm.js code and can be used to configure Emscripten output. Must follow base path (which may be set to null or undefined).
  • Node-style callback with 2 parameters:
    • Error if present, otherwise null.
    • Binary code exports object containing C++ classes.

nbind can be initialized synchronously on Node.js and asynchronously on browsers and Node.js. Purely synchronous is easier but not as future-proof:

var nbind = require('nbind');
var lib = nbind.init().lib;

// Use the library.

Using a callback also supports asynchronous initialization:

var nbind = require('nbind');

nbind.init(function(err, binding) {
  var lib = binding.lib;

  // Use the library.
});

The callback passed to init currently gets called synchronously in Node.js and asynchronously in browsers. To avoid releasing zalgo you can for example wrap the call in a bluebird promise:

var bluebird = require('bluebird');
var nbind = require('nbind');

bluebird.promisify(nbind.init)().then(function(binding) {
  var lib = binding.lib;

  // Use the library.
});

Using nbind headers

There are two possible files to include:

  • nbind/api.h for using types from the nbind namespace such as JavaScript callbacks inside your C++ code.
    • #include before your own class definitions.
    • Causes your code to depend on nbind.
  • nbind/nbind.h for exposing your C++ API to JavaScript.
    • #include after your own class definitions to avoid accidentally invoking its macros.
    • The header automatically hides itself if not targeting Node.js or asm.js.
    • Safe to use in any projects.

Use #include "nbind/nbind.h" at the end of your source file with only the bindings after it. The header defines macros with names like construct and method that may otherwise break your code or conflict with other headers.

It's OK to include nbind/nbind.h also when not targeting any JavaScript environment. node-gyp defines a BUILDING_NODE_EXTENSION macro and Emscripten defines an EMSCRIPTEN macro so when those are undefined, the include file does nothing.

Use #include "nbind/api.h" in your header files to use types in the nbind namespace if you need to report errors without throwing exceptions, or want to pass around callbacks or objects.

You can use an #ifdef NBIND_CLASS guard to skip your nbind export definitions when the headers weren't loaded.

Example that uses an nbind callback in C++ code:

1-headers.cc

#include <string>
#include <iostream>

// For nbind::cbFunction type.
#include "nbind/api.h"

class HeaderExample {

public:

  static void callJS(nbind::cbFunction &callback) {
    std::cout << "JS says: " << callback.call<std::string>(1, 2, 3);
  }

};

// For NBIND_CLASS() and method() macros.
#include "nbind/nbind.h"

#ifdef NBIND_CLASS

NBIND_CLASS(HeaderExample) {
  method(callJS);
}

#endif

Example used from JavaScript:

1-headers.js

var nbind = require('nbind');

var lib = nbind.init().lib;

lib.HeaderExample.callJS(function(a, b, c) {
  return('sum = ' + (a + b + c) + '\n');
});

Run the example with node 1-headers.js after installing. It prints:

JS says: sum = 6

Functions

Functions not belonging to any class are exported inside an NBIND_GLOBAL block with a macro call function(functionName); which takes the name of the function as an argument (without any quotation marks). The C++ function gets exported to JavaScript with the same name, or it can be renamed by adding a second argument (with quotation marks): function(cppFunctionName, "jsExportedName");

If the C++ function is overloaded, multifunction macro must be used instead. See overloaded functions.

Note: you cannot put several function(...); calls on the same line! Otherwise you'll get an error about redefining a symbol.

Example:

6-functions.cc

#include <iostream>

void sayHello(std::string name) {
  std::cout << "Hello, " << name << "!\n";
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(sayHello);
}

Example used from JavaScript:

6-functions.js

var nbind = require('nbind');
var lib = nbind.init().lib;

lib.sayHello('you');

Classes and constructors

The NBIND_CLASS(className) macro takes the name of your C++ class as an argument (without any quotation marks), and exports it to JavaScript using the same name. It's followed by a curly brace enclosed block of method exports, as if it was a function definition.

The class can be renamed on the JavaScript side by passing a string as a second argument. This is especially useful for binding a template class specialization with a more reasonable name: NBIND_CLASS(Data<int>, "IntData")

Constructors are exported with a macro call construct<types...>(); where types is a comma-separated list of arguments to the constructor, such as int, int. Calling construct multiple times allows overloading it, but each overload must have a different number of arguments.

Constructor arguments are the only types that nbind cannot detect automatically.

Example with different constructor argument counts and types:

2-classes.cc

#include <iostream>

class ClassExample {

public:

  ClassExample() {
    std::cout << "No arguments\n";
  }
  ClassExample(int a, int b) {
    std::cout << "Ints: " << a << " " << b << "\n";
  }
  ClassExample(const char *msg) {
    std::cout << "String: " << msg << "\n";
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(ClassExample) {
  construct<>();
  construct<int, int>();
  construct<const char *>();
}

Example used from JavaScript:

2-classes.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var a = new lib.ClassExample();
var b = new lib.ClassExample(42, 54);
var c = new lib.ClassExample("Don't panic");

Run the example with node 2-classes.js after installing. It prints:

No arguments
Ints: 42 54
String: Don't panic

Inheritance

When a C++ class inherits another, the inherit macro can be used to allow calling parent class methods on the child class, or passing child class instances to C++ methods expecting parent class instances.

Internally JavaScript only has prototype-based single inheritance while C++ supports multiple inheritance. To simulate it, nbind will use one parent class as the child class prototype, and copy the contents of the other parents to the prototype. This has otherwise the same effect, except the JavaScript instanceof operator will return true for only one of the parent classes.

Example:

NBIND_CLASS(Child) {
    inherit(FirstParent);
    inherit(SecondParent);
}

Methods and properties

Methods are exported inside an NBIND_CLASS block with a macro call method(methodName); which takes the name of the method as an argument (without any quotation marks). The C++ method gets exported to JavaScript with the same name.

If the C++ method is overloaded, multimethod macro must be used instead. See overloaded functions.

Properties should be accessed through getter and setter functions.

Data types of method arguments and its return value are detected automatically so you don't have to specify them. Note the supported data types because using other types may cause compiler errors that are difficult to understand.

If the method is static, it becomes a property of the JavaScript constructor function and can be accessed like className.methodName(). Otherwise it becomes a property of the prototype and can be accessed like obj = new className(); obj.methodName();

Example with a method that counts a cumulative checksum of ASCII character values in strings, and a static method that processes an entire array of strings:

3-methods.cc

#include <string>
#include <vector>

class MethodExample {

public:

  unsigned int add(std::string part) {
    for(char &c : part) sum += c;

    return(sum);
  }

  static std::vector<unsigned int> check(std::vector<std::string> list) {
    std::vector<unsigned int> result;
    MethodExample example;

    for(auto &&part : list) result.push_back(example.add(part));

    return(result);
  }

  unsigned int sum = 0;

};

#include "nbind/nbind.h"

NBIND_CLASS(MethodExample) {
  construct<>();

  method(add);
  method(check);
}

Example used from JavaScript, first calling a method in a loop from JS and then a static method returning an array:

3-methods.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var parts = ['foo', 'bar', 'quux'];

var checker = new lib.MethodExample();

console.log(parts.map(function(part) {
  return(checker.add(part));
}));

console.log(lib.MethodExample.check(parts));

Run the example with node 3-methods.js after installing. It prints:

[ 324, 633, 1100 ]
[ 324, 633, 1100 ]

The example serves to illustrate passing data. In practice, such simple calculations are faster to do in JavaScript rather than calling across languages because copying data is quite expensive.

Overloaded functions

The function() and method() macroes cannot distinguish between several overloaded versions of the same function or method, causing an error. In this case the multifunction() and multimethod() macroes must be used.

Their second parameter is a list of argument types wrapped in an args() macro to select a single overloaded version.

For example consider an overloaded method:

void test(unsigned int x) const;
void test(unsigned int x, unsigned int y) const;

In bindings, one of the versions needs to be explicitly selected. The second of the two would be referenced like:

multimethod(test, args(unsigned int, unsigned int));

As always, the return type and method constness are autodetected.

For calling from JavaScript, additionally each overload needs to have a distinct name. For renaming an overload JavaScript will see, the binding code is like:

multimethod(test, args(unsigned int, unsigned int), "test2");

You can then write a JavaScript wrapper to inspect arguments and select which overload to call. The reason for this is, that nbind binds a JavaScript property to a single C++ function pointer, which wraps one overloaded version of the function with type conversion code.

Otherwise, it would need to generate a new C++ function that also checks the arguments. This would result in a larger native binary without any speed advantage.

Getters and setters

Property getters are exported inside an NBIND_CLASS block with a macro call getter(getterName) with the name of the getter method as an argument. nbind automatically strips a get/Get/get_/Get_ prefix and converts the next letter to lowercase, so for example getX and get_x both would become getters of x to be accessed like obj.x

Property setters are exported together with getters using a macro call getset(getterName, setterName) which works much like getter(getterName) above. Both getterName and setterName are mangled individually so you can pair getX with set_x if you like. From JavaScript, ++obj.x would then call both of them to read and change the property.

Example class and property with a getter and setter:

4-getset.cc

class GetSetExample {

public:

  void setValue(int value) { this->value = value; }
  int getValue() { return(value); }

private:

  int value = 42;

};

#include "nbind/nbind.h"

NBIND_CLASS(GetSetExample) {
  construct<>();

  getset(getValue, setValue);
}

Example used from JavaScript:

4-getset.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var obj = new lib.GetSetExample();

console.log(obj.value++); // 42
console.log(obj.value++); // 43

Run the example with node 4-getset.js after installing.

Passing data structures

nbind supports automatically converting between JavaScript arrays and C++ std::vector or std::array types. Just use them as arguments or return values in C++ methods.

Note that data structures don't use the same memory layout in both languages, so the data always gets copied which takes more time for more data. For example the strings in an array of strings also get copied, one character at a time. In asm.js data is copied twice, first to a temporary space using a common format both languages can read and write.

Callbacks

Callbacks can be passed to C++ methods by simply adding an argument of type nbind::cbFunction & to their declaration.

They can be called with any number of any supported types without having to declare in any way what they accept. The JavaScript code will receive the parameters as JavaScript variables to do with them as it pleases.

A callback argument arg can be called like arg("foobar", 42); in which case the return value is ignored. If the return value is needed, the callback must be called like arg.call<type>("foobar", 42); where type is the desired C++ type that the return value should be converted to. This is because the C++ compiler cannot otherwise know what the callback might return.

Warning: while callbacks are currently passed by reference, they're freed after the called C++ function returns! That's intended for synchronous functions like Array.map which calls a callback zero or more times and then returns. For asynchronous functions like setTimeout which calls the callback after it has returned, you need to copy the argument to a new nbind::cbFunction and store it somewhere.

Using objects

C++ objects can be passed to and from JavaScript using different parameter and return types in C++ code:

  • by reference using pointers or references (optionally const)
  • by value

Note: currently passing objects by pointer on Node.js requires the class to have a "copy constructor" initializing itself from a pointer. This will probably be fixed later.

Returned pointers and references can be const, in which case calling their non-const methods or passing them as non-const parameters will throw an error. This prevents causing undefined behaviour corresponding to C++ code that wouldn't even compile.

Using pointers and references is particularly:

  • dangerous because the pointer may become invalid without JavaScript noticing it.
  • annoying in asm.js because browsers give no access to the garbage collector, so memory may leak when pointers become garbage without C++ noticing it. Smart pointers are not supported until a workaround for this is implemented.

Passing data by value using value objects solves both issues. They're based on a toJS function on the C++ side and a fromJS function on the JavaScript side. Both receive a callback as an argument, and calling it with any parameters calls the constructor of the equivalent type in the other language.

The callback on the C++ side is of type nbind::cbOutput. Value objects are passed through the C++ stack to and from the exported function. nbind uses C++11 move semantics to avoid creating some additional copies on the way.

The equivalent JavaScript constructor must be registered on the JavaScript side by calling binding.bind('CppClassName', JSClassName) so that nbind knows which types to translate between each other.

Example with a class Coord used as a value object, and a class ObjectExample which uses objects passed by values and references:

5-objects.cc

#include <iostream>

#include "nbind/api.h"

class Coord {

public:

  Coord(signed int x = 0, signed int y = 0) : x(x), y(y) {}
  explicit Coord(const Coord *other) : x(other->x), y(other->y) {}

  void toJS(nbind::cbOutput output) {
    output(x, y);
  }

  signed int getX() { std::cout << "Get X\n"; return(x); }
  signed int getY() { std::cout << "Get Y\n"; return(y); }

  void setX(signed int x) { this->x = x; }
  void setY(signed int y) { this->y = y; }

  signed int x, y;

};

class ObjectExample {

public:

  static void showByValue(Coord coord) {
    std::cout << "C++ value " << coord.x << ", " << coord.y << "\n";
  }

  static void showByRef(Coord *coord) {
    std::cout << "C++ ref " << coord->x << ", " << coord->y << "\n";
  }

  static Coord getValue() {
    return(Coord(12, 34));
  }

  static Coord *getRef() {
    static Coord coord(56, 78);
    return(&coord);
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(Coord) {
  construct<>();
  construct<const Coord *>();
  construct<signed int, signed int>();

  getset(getX, setX);
  getset(getY, setY);
}

NBIND_CLASS(ObjectExample) {
  method(showByValue);
  method(showByRef);
  method(getValue);
  method(getRef);
}

Example used from JavaScript:

5-objects.js

var nbind = require('nbind');

var binding = nbind.init();
var lib = binding.lib;

function Coord(x, y) {
  this.x = x;
  this.y = y;
}

Coord.prototype.fromJS = function(output) {
  output(this.x, this.y);
}

Coord.prototype.show = function() {
  console.log('JS value ' + this.x + ', ' + this.y);
}

binding.bind('Coord', Coord);

var value1 = new Coord(123, 456);
var value2 = lib.ObjectExample.getValue();
var ref = lib.ObjectExample.getRef();

lib.ObjectExample.showByValue(value1);
lib.ObjectExample.showByValue(value2);
value1.show();
value2.show();

lib.ObjectExample.showByRef(ref);
console.log('JS ref ' + ref.x + ', ' + ref.y);

Run the example with node 5-objects.js after installing. It prints:

C++ value 123, 456
C++ value 12, 34
JS value 123, 456
JS value 12, 34
C++ ref 56, 78
Get X
Get Y
JS ref 56, 78

Type conversion

Parameters and return values of function calls between languages are automatically converted between equivalent types:

JavaScriptC++
number(un)signed char, short, int, long
numberfloat, double
number or bignum(un)signed long, long long
booleanbool
stringconst (unsigned) char *
stringstd::string
Arraystd::vector<type>
Arraystd::array<type, size>
Functionnbind::cbFunction
(only as a parameter)
See Callbacks
nbind-wrapped pointerPointer or reference to an
instance of any bound class
See Using objects
Instance of any prototype
(with a fromJS method)
Instance of any bound class
(with a toJS method)
See Using objects
ArrayBuffer(View), Int*Array
or Buffer
nbind::Buffer struct
(data pointer and length)
See Buffers

Type conversion is customizable by passing policies as additional arguments to construct, function or method inside an NBIND_CLASS or NBIND_GLOBAL block. Currently supported policies are:

  • nbind::Nullable() allows passing null as an argument when a C++ class instance is expected. The C++ function will then receive a nullptr.
  • nbind::Strict() enables stricter type checking. Normally anything in JavaScript can be converted to number, string or boolean when expected by a C++ function. This policy requires passing the exact JavaScript type instead.

Type conversion policies are listed after the method or function names, for example:

NBIND_CLASS(Reference) {
    method(reticulateSplines, "reticulate", nbind::Nullable());
    method(printString, nbind::Strict());
}

Buffers

Transferring large chunks of data between languages is fastest using typed arrays or Node.js buffers in JavaScript. Both are accessible from C++ as plain blocks of memory if passed in through the nbind::Buffer data type which has the methods:

  • data() returns an unsigned char * pointing to a block of memory also seen by JavaScript.
  • length() returns the length of the block in bytes.
  • commit() copies data from C++ back to JavaScript (only needed with Emscripten).

This is especially useful for passing canvas.getContext('2d').getImageData(...).data to C++ and drawing to an on-screen bitmap when targeting Emscripten or Electron.

Example:

#include "nbind/api.h"

void range(nbind::Buffer buf) {
  size_t length = buf.length();
  unsigned char *data = buf.data();

  if(!data || !length) return;

  for(size_t pos = 0; pos < length; ++pos) {
    data[pos] = pos;
  }

  buf.commit();
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(range);
}

Example used from JavaScript:

var nbind = require('nbind');
var lib = nbind.init().lib;

var data = new Uint8Array(16);
lib.range(data);

console.log(data.join(' '));

It prints:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

64-bit integers

Normally C++ 64-bit integer types are first converted to double and then to JavaScript number which can only hold 53 bits of precision, but it's possible to preserve all bits by using a bignum class. It should have a constructor taking the following arguments:

  • Integer containing 32 bits from the least important half.
  • Integer containing 32 bits from the most important half.
  • Boolean, true if the number is negative.

It should also have a fromJS function which takes a callback, and calls it with those same arguments to pass the data back to C++ when needed.

An example implementation also capable of printing 64-bit numbers to strings in bases 2, 4, 10 and 16 is included.

Error handling

You can use the NBIND_ERR("message here"); macro to report an error before returning from C++ (#include "nbind/api.h" first). It will be thrown as an error on the JavaScript side (C++ environments like Emscripten may not support throwing exceptions, but the JavaScript side will).

Publishing on npm

Make sure your package.json file has at least the required emcc-path and install scripts:

  "scripts": {
    "emcc-path": "emcc-path",

    "install": "autogypi && node-gyp configure build"
  }

The dependencies section should have at least:

  "dependencies": {
    "autogypi": "^0.2.2",
    "nbind": "^0.2.1",
    "node-gyp": "^3.3.1"
  }

Your package should also include binding.gyp and autogypi.json files.

Shipping an asm.js fallback

nbind-example-universal is a good minimal example of compiling a native Node.js addon if possible, and otherwise using a pre-compiled asm.js version.

It has two temporary build directories build/native and build/asmjs, for compiling both versions. nbind provides a binary copyasm that can then be used to copy the compiled asm.js library into a nicer location for publishing inside the final npm package.

Note that the native version should be compiled in the install script so it runs for all users of the package, and the asm.js version should be compiled in the prepublish script so it gets packaged in npm for usage without the Emscripten compiler. See the example package.json file.

Using in web browsers

nbind-example-universal is a good minimal example also of calling compiled asm.js code from inside web browsers. The simplest way to get nbind working is to add these scripts in your HTML code as seen in the example index.html:

<script src="nbind.js"></script>

<script>
  nbind.init(function(err, binding) {
    var lib = binding.lib;

    // Use the library.
  });
</script>

Make sure to fix the path to nbind.js on the first line if necessary.

Using with TypeScript

nbind has a fully typed API for interacting with C++ code and it can also automatically generate .d.ts files for your C++ classes and functions. This gives you effortless bindings with compile time type checking for calls from JavaScript to Node.js addons and asm.js modules.

All you have to do is compile your C++ code and run the included ndts tool to create the type definitions:

npm run -- node-gyp configure build
npm run -s -- ndts . > lib-types.d.ts

When run in this way, the first argument of ndts is a path from the package root to the binding.gyp file. Typically the file is in the root so the correct path is .

Now you can load the C++ code from TypeScript in three different ways. First import nbind (which also loads the C++ code) and types generated by ndts:

import * as nbind from 'nbind';
import * as LibTypes from './lib-types';

Then choose your favorite way to initialize it:

Purely synchronous:

const lib = nbind.init<typeof LibTypes>().lib;

// Use the library.

Asynchronous-aware:

nbind.init((err: any, binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Promise-based:

import * as bluebird from 'bluebird';

bluebird.promisify(nbind.init)().then((binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Note how there is a type argument <typeof LibTypes> for the init call in all of the examples. It defines types of binding.lib contents, which coming from C++ are otherwise unknown to the TypeScript compiler. You can import the types from a file generated by ndts or just use <any> to disable typing.

For example if you have a C++ class:

struct C : public A, public B {
    A *getA();

    static uint32_t reticulate();
};

And bind it like:

NBIND_CLASS(C) {
    inherit(A);
    inherit(B);

    construct<>();

    method(reticulate);

    getter(getA);
}

ndts will generate the following typings:

export interface _C extends A, B {}
export var _C: { new(): _C };

export class C extends _C {
    /** C(); */
    constructor();

    /** static uint32_t reticulate(); */
    static reticulate(): number;

    /** A * a; -- Read-only */
    a: A;
}

The additional interface _C is generated in this case to support multiple inheritance, because C extends both A and B.

All the tests are written in TypeScript so if you run:

git clone https://github.com/charto/nbind.git
cd nbind
npm install
npm test

You can then open test/test.ts in a TypeScript IDE and see the generated typings in action.

Binding plain C

nbind generates bindings using C++ templates for compile-time introspection of argument and return types of functions and methods.

Since plain C doesn't have templates, there's no standard way to have a C compiler generate new wrapper code for type conversion and output type information available at run-time.

The easiest way to use nbind with C is to write a C++ wrapper calling the C code, and use nbind with that.

Mapping idiomatic C to JavaScript classes may require some manual work, since it's common to reinvent new ways to do object-oriented programming, usually by using structs as classes and simulating methods by passing struct pointers to functions. C++ classes and methods should be used for these.

A good example is libui-node which uses nbind to generate bindings for libui, mainly a C library.

Binding external libraries

If you have external library source code, you should compile it separately into a library first, and then link your Node.js addon with it. If the library has an installation script and the addon is only intended for your own use or other users are willing to do some extra steps, it's easiest to install the library globally first.

For best user experience, libui-node is an example of distributing an external library together with your package.

For creating the actual bindings, see for example this and this message and a tutorial for getting the vg library working.

Debugging

In the browser it can be difficult to stop and debug at the correct spot in optimized C++ code. nbind provides an _nbind_debug() function in api.h that you can call from C++ to invoke the browser's debugger when using asm.js.

For debugging a Node.js addon, if you would normally test it like node test.js, you can instead use gdb node and type run test.js in the GDB prompt. Then in case of a crash, it will show where it happened, inspect the stack etc.

You should also modify nbind.gypi (inside nbind's src directory) and possibly your own binding.gyp, to remove any -O? flags and instead add a -g flag, then remove the build directory and recompile. This allows GDB to show much more information.

Alternatives

Very similar:

Less similar:

Authors

  • Juha Järvi, befunge

Author: charto
Source Code: https://github.com/charto/nbind
License: MIT License
#cpluplus #c 

What is GEEK

Buddha Community

Nbind: Magical Headers That Make Your C++ Library Accessible From JS

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Understanding CSS's !important declaration

!important in CSS is a special notation that we can apply to a CSS declaration to override other conflicting rules for the matching selector.

When we work on web projects, it is natural that we have some style declarations that other styles overrule.

This is not an issue for an experienced developer who understands the core mechanism of CSS. However, it can be difficult for beginners to understand why the style declarations they expect are not applied by the browser.

So, instead of them focusing on resolving the issue naturally, they tend to go for the quick fix by adding the !important declaration to enforce the style they expect. While this approach might work for that moment, it can also initiate another complex problem.

In this guide, we will review the following, including how to use !important and when we should use it:

  • The CSS core mechanism
  • Understanding the !important declaration before we use it
  • :is() and other related pseudo-class functions
  • When exactly can we use !importantdeclaration?
    • Utility classes
    • The style rules we cannot override

Enough said, let’s dive in.

The CSS core mechanism

Understanding the core principles of CSS will naturally enable us to know when it’s obvious to use the !important declaration. In this section, we will walk through some of these mechanisms.

Consider the HTML and CSS code below, what color do you think the heading text will be?

First, the HTML:

<h2 class="mytitle">This is heading text</h2>

Then, the CSS:

h2 {
  color: blue;
}
h2 {
  color: green;
}

The text will render green! This is basic CSS fundamental. With the CSS cascade algorithm, the ordering of CSS rules matters. In this case, the declaration that comes last in the source code wins.

Normally, this is logical. In the first place, we should not repeat the same selector as we did above. CSS does not want repetition, so it uses the last declaration rule.

However, there are cases whereby we create generic styles for the root elements, like the h2, and then add classes to style specific elements. Let’s consider the following example as well, starting with the HTML:

<h2>This is heading text</h2>
<h2 class="mytitle">This is heading text</h2>

Then, let’s see the CSS:

.mytitle {
  color: blue;
}
h2 {
  color: green;
}

In the above code, the first h2 element has no class applied, so it is obvious that it gets the green color of the h2 selector.

However, the second h2 element uses the rule for the class selector, .mytitle, even when the element selector rule comes last in the CSS code. The reason for that is that the class selector has a higher specificity when compared to the element selector.

In other words, the weight applied to the declaration in a class selector is more than element selector’s weight.

Similarly, the declaration in an ID selector is more than that of the class selector. In this case, the red color in the code below takes precedence:

<h2 id="maintitle" class="mytitle">This is heading text</h2> 

Followed by the CSS:

.mytitle {
  color: blue;
}
#maintitle {
  color: red;
}
h2 {
  color: green;
}

Furthermore, an inline style attribute takes precedence over the ID selector, starting with the HTML:

<h2 id="maintitle" style="color: black;" class="mytitle">This is heading text</h2> 

 

Then followed by the CSS:

.mytitle {/*...*/}

#maintitle {/*...*/}

h2 {/*...*/}

This is the ideal priority flow in CSS and must be maintained to avoid anomalies. The !important declaration most of the time comes when we are oblivious of these basic rules.

The inline style attribute and each of the selectors have values that browsers assign to them. That way, it knows which one has higher or lower priority. Think of this value as a number of four single digits with the style attribute assigned the strongest weight value of 1000.

This follows the ID with a value of 0100, then class with 0010, and finally the element selector with 0001.

Sometimes we can combine selectors targeting specific elements, as seen in the example below:

<h2 id="maintitle" class="mytitle">This is heading text</h2> 

 

Followed by the CSS:

h2.mytitle {
  color: blue;
}
#maintitle {
  color: red;
}
h2 {
  color: green;
}

The specificity of the h2.mytitle selector in the CSS above is the addition of h2 and .mytitle. That is, 0001 + 0010 = 0011. This total value, however, is less than that of the #maintitle ID that is 0100.

So, the browser uses the declaration in the ID selector to override other conflicting rules. In a case of equal weight, the last rule declaration wins.

Now that we know which rules are most relevant and why the browser applies them, it will become naturally obvious whether or not to use this !important declaration.

Understanding the !important declaration before we use it

Before we consider using the !important notation, we must ensure that we follow the specificity rule and use the CSS cascade.

In the code below, we have the h2 and h3 elements styled to be a red color:

<h2 class="mytitle">This is heading II text</h2>
<h3 class="mytitle">This is heading III text</h3>

Then, .mytitle in CSS:

.mytitle {
  color: red;
}

But, let’s say at some point, we want to give the h3 element a blue color. Adding a style rule like the one below would not change the color because the class has more weight and it’s more specific than the element selector, as we’ve learned:

.mytitle {...}
h3 {
  color: blue;
}

However, using the !important on the lesser weight makes the browser enforce that declaration over other conflicting rules:

.mytitle {...}
h3 {
  color: blue !important;
}

This is because the !important notation increases the weight of the declaration in the cascade order of precedence. What this means is that we’ve disrupted the normal priority flow. Hence, bad practice, and can lead to difficulties in code maintenance and debugging.

If at some other point, we want to override the above important rule, we can apply another !important notation on a declaration with higher specificity (or the same if it is lower down in the source). It can then lead to something like this:

h3 {
  color: blue !important;
}

/* several lines of rules */

.mytitle {
  color: green !important;
}

This is bad and should be avoided. Instead, we should check if:

  1. Rearranging the rule or rewriting the selectors can solve the cascading issue
  2. Increasing the specificity of the target element can solve the issue

Well, let’s find out. Back to our style rules, we can enforce a blue color on the h3 element by increasing the specificity score.

As seen below, we can combine selectors until their specificity score supersedes the conflicting rule. The h3.mytitle selector gives a specificity score of 0011, which is more than the .mytitle of 0010 score:

.mytitle {...}
h3.mytitle {
  color: blue;
}

As we can see, instead of using the !important declaration to enforce a rule, we focus on increasing the specificity score.

:is() and other related pseudo-class functions

Sometimes, we may trace issues to a pseudo-class function. So, knowing how it works can save us a lot of stress. Let’s see another example.

Imagine we are working on a project and see the following code:

<h1 id="header">
  heading <span>span it</span>
  <a href="#">link it</a>
</h1>
<p class="paragraph">
  paragraph <span>span it</span>
  <a href="">link it</a>
</p>

Using the following CSS rules gives us the output after:

:is(#header, p) span,
:is(#header, p) a {
  color: red;
}

Output Heading Span It Link It

Now, let’s say we want to give the span and the link text in the paragraph another color of blue. We can do this by adding the following rule:

.paragraph span,
.paragraph a {
  color: blue;
}

The earlier rule will override the blue color despite being further down the line:

Blue Color

As a quick fix, we can enforce our blue color by using the !important notation like so:

:is(#header, p) span,
:is(#header, p) a {...}

.paragraph span,
.paragraph a {
  color: blue !important;
}

But, as you may guess, that is bad practice, so we must not be quick to use the !important notation. Instead, we can start by analyzing how every selector works. The :is() is used in the code is a pseudo-class function for writing mega selectors in a more compressed form.

So, here is the following rule in the above code:

:is(#header, p) span,
:is(#header, p) a {
  color: red;
}

Which is equivalent to the following:

#header span,
p span,
#header a,
p a {
  color: red;
}

So, why is .paragraph span and .paragraph a not overriding the color despite having a specificity score of 0011, which is higher than 0002 of the p span and p a.

Well, every selector in the :is() uses the highest specificity in the list of arguments. In that case, both the #header and the p in the :is(#header, p) uses the specificity score of the #header, which is 0100. Thus, the browser sticks to its value because it has a higher specificity.

Thus, anytime we see this type of conflict, we are better off not using the pseudo-class function and sticking to its equivalent like the following:

#header span,
p span,
#header a,
p a {
  color: red;
}

Now, we should be able to see the expected result without using the !important notation that disrupts cascade order.

Result With Important Notation

You can see for yourself on CodeSandbox.

When exactly can we use !important declaration?

Below are a few occasions where using the !important notation is recommended.

Utility classes

Assuming we want to style all buttons on a page to look the same, we can write a CSS rule that can be reused across a page. Let’s take a look at the following markup and style below:

<p>Subscribe button : <a class="btn" href="#">Subscribe</a></p>

<section class="content">
  <p>
    This <a href="#" class="btn">button</a> style is affected by a higher
    specificity value .
  </p>
  A link here: <a href="#">Dont click</a>
</section>

Followed by the CSS:

.btn {
  display: inline-block;
  background: #99f2f5;
  padding: 8px 10px;
  border: 1px solid #99f2f5;
  border-radius: 4px;
  color: black;
  font-weight: normal;
  text-decoration: none;
}

.content a {
  color: blue;
  font-weight: bold;
  text-decoration: underline;
}

In the above code, we can see that the button link within the section element is targeted by both selectors in the CSS. And, we learned that for conflicting rules, the browser will use the most specific rule. As we expect, .content a has a score of 0011 while .btn has a score of 0010.

The page will look like this:

Example Of Subscribe Page

In this case, we can enforce the .btn rule by adding the !important notation to the conflicting declarations like this:

.btn {
  /* ... */
  color: black !important;
  font-weight: normal !important;
  text-decoration: none !important;
}

The page now looks as we expect:

New Subscribe Page

See for yourself on CodeSandbox.

The style rules we cannot override

This mostly happens when we don’t have total control over the working code. Sometimes, when we work with a content management system like WordPress, we may find that an inline CSS style in our WordPress theme is overruling our custom style.

In this case, the !important declaration is handy to override the theme inline style.

Conclusion

The !important declaration is never meant to be used as we desire. We must only use it if absolutely necessary, such as a situation where we have less control over the code or very extreme cases in our own code.

Whether or not we use it depends on how we understand the core CSS mechanism, and in this tutorial, we covered that as well.

I hope you enjoyed reading this post. If you have questions or contributions, share your thought in the comment section and remember to share this tutorial around the web.

Source: https://blog.logrocket.com/understanding-css-important-declaration/

#css 

Web  Dev

Web Dev

1652837384

Build a Single Page App with Laravel 9, Jetstream, Vuejs, Inertiajs, MySQL, Tailwind CSS and Docker

In this tutorial, you will learn how to build a single page application. I'll take you through the process step by step, using cutting edge technologies like Laravel 9, Jetstream, Vuejs, Inertiajs, MySQL, Tailwind CSS, and Docker.

Let's get started.

What you need to follow this guide:

To follow along you will need:

  • a computer
  • to know how to install software
  • a basic understanding of HTML, CSS, JavaScript, and PHP
  • knowledge of at least one JavaScript framework and an understanding of the MVC design pattern.

This guide is organized into 10 chapters and is based off a live coding series that I record. The live coding series is completely unscripted, so there will be bugs and gotchas there that you won't find in this guide.

You can find the complete playlist at the end of this article.

Everything here should just work, but if it doesn't feel free to ask for help by joining my community on Slack. There you can share code snippets and chat with me directly.

Table of Contents

  • What Tech Are We Using?
  • How to Setup Your Machine
  • How to build the app with Laravel 9, Laravel Sail, Jetstram, Inertia and Vue3
  • How to Refactor the Admin Dashboard and Create New Admin Pages
  • How to Submit Forms with Files
  • How to Add the Form to the Component
  • How to Store Data
  • How to Update Operations
  • How to Delete a Resourse
  • Wrap up and what's next
  • Conclusion

Original article source at https://www.freecodecamp.org

What Tech Are We Using?

First, let's go over the different tools we'll be using in this project.

Docker

Docker is a set of platform as a service products that use OS-level virtualization to deliver software in packages called containers.

To simplify this concept, Docker lets you package applications and dependencies in a container.

A containerized application allows you to have a flexible development environment so that you can run different applications without worrying about dependencies, their requirements, and conflicts between different versions. You can easily run applications that, for instance, require two different versions of PHP and MySQL.

Each team member can quickly reproduce the same environment of your application by simply running the same container's configuration.

If you want to learn more about Docker, its Documentation is a great place to start.

Here's a Handbook on Docker essentials, as well, so you can practice your skills.

Mysql

MySQL is an open-source relational database management system. You can use it to organize data into one or more tables with data that may be related to each other.

We need to store data somewhere and here is where MySQL comes into play.

Here are the Docs if you want to read up more. Here's a full free course on MySQL if you want to dive deeper.

Laravel

Laravel is a free, open-source PHP web framework that helps you develop web applications following the model–view–controller architectural pattern.

Laravel is an amazing PHP framework that you can use to create bespoke web applications.

Here's the Laravel Documentation for more info, and here's a full project-based course to help you learn Laravel.

Laravel Sail

Laravel Sail is a lightweight command-line interface for interacting with Laravel's default Docker development environment.

Sail provides a great starting point for building a Laravel application using PHP, MySQL, and Redis without requiring prior Docker experience.

Usually, creating a development environment to build such applications means you have to install software, languages, and frameworks on your local machine – and that is time-consuming. Thanks to Docker and Laravel Sail we will be up and running in no time!

Laravel Sail is supported on macOS, Linux, and Windows via WSL2.

Here's the Documentation if you want to read up on it.

Laravel Jetstream

When building web applications, you likely want to let users register and log in to use your app. That is why we will use Jetstream.

Laravel Jetstream is a beautifully designed application starter kit for Laravel and provides the perfect starting point for your next Laravel application.

It uses Laravel Fortify to implement all the back end authentication logic.
Here are the Docs.

Vuejs

Vue.js is an open-source model–view–ViewModel front end JavaScript framework for building user interfaces and single-page applications.

Vue is a fantastic framework that you can use as a stand-alone to build single-page applications, but you can also use it with Laravel to build something amazing.

Here's the Vue Documentation if you want to read up. And here's a great Vue course to get you started.

Inertia JS

Inertia is the glue between Laravel and Vuejs that we will use to build modern single-page applications using classic server-side routing.

You can learn more about it in the Documentation here.

Tailwind

Tailwind CSS is a utility-first CSS framework packed with classes like flex, pt-4, text-center, and rotate-90 that you can use to build any design, directly in your markup

We'll use it in this project to build our design. Here's a quick guide to get you up and running if you aren't familiar with Tailwind.

How to Set Up Your Machine

To follow along with my live coding (and this tutorial), you will need to install Docker desktop on your machine. If you are using Windows, you will also need to enable WSL in your system settings.

Visit the Docker getting started page to install Docker Desktop.

If you are on Windows, enable WSL2 by following the steps here.

If you have any trouble, feel free to reach out or join my community on Slack to get help.

Laravel Installation with Sail

If you have successfully installed Docker Desktop on your machine, we can open the terminal and install Laravel 9.

Open a terminal window and browse to a folder where you want to keep your project. Then run the command below to download the latest Laravel files. The command will put all files inside a folder called my-example-app, which you can tweak as you like.

# Download laravel
curl -s "https://laravel.build/my-example-app" | bash
# Enter the laravel folder
cd my-example-app

Deploy Laravel on Docker using the sail up command

With Docker Desktop up and running, the next step is to start Laravel sail to build all the containers required to run our application locally.

Run the following command from the folder where all Laravel files have been downloaded:

vendor/bin/sail up

It will take a minute. Then visit http://localhost and you should see your Laravel application.

If you run sail up and you get the following error, it is likely that you need to update Docker Desktop:

ERROR: Service 'laravel.test' failed to build:

How to Build the App with Laravel 9, Laravel Sail, Jetstram, Inertia and Vue3

In this section, we will define a basic roadmap, install Laravel 9 with Laravel Sail, Run sail, and build the containers.

I will also take you on a tour of Laravel Sail and the sail commands.

Then we will install Jetstream and scaffold Vue and Inertia files and have a look at the files and available features.

Next, we will populate our database and add the front end provided by Jetstream to register an account and log into a fresh Laravel application.

Finally, we will have a look at the Jetstream dashboard, and the Inertia/Vue Components and then start playing around.

Along the way, we'll disable the registration, enable the Jetstream user profile picture feature, and then add our first Inertia page where we'll render some data taken from the database.

Here's the live coding video if you want to follow along that way:

 

And if you prefer following along in this written tutorial, here are all the steps.

Just a reminder – you should have Laravel installed with Sail and have Docker set up on your machine. You can follow the steps above to do so if you haven't already.

Laravel Sail Overview – Sail Commands

With Laravel Sail installed, our usual Laravel commands have sligtly changed.

For instance, instead of running the Laravel artisan command using PHP like php artisan, we now have to use Sail, like so: sail artisan.

The sail artisan command will return a list of all available Laravel commands.

Usually, when we work with Laravel, we also have to run the npm and composer commands.

Again, we need to prefix our commands with sail to make them run inside the container.

Below you'll find a list of some commands you will likely have to run:

# Interact with the database - run the migrations
sail artisan migrate # It was: php artisan migrate
# Use composer commands
sail composer require <packageName> # it was: composer require <packageName>
# Use npm commands
sail npm run dev # it was: npm run dev

You can read more in the Sail documentation.

Install Jetstream and Scaffold Vue and Inertia

Let's now install the Laravel Jetstream authentication package and use the Inertia scaffolding with Vue3.

cd my-example-app
sail composer require laravel/jetstream 

Remember to prefix the composer command with sail.

The command above has added a new command to Laravel. Now we need to run it to install all the Jetstream components:

sail artisan jetstream:install inertia

Next we need to compile all static assets with npm:

sail npm install
sail npm run dev

Before we can actually see our application, we will need to run the database migrations so that the session table, required by Jetstream, is present.

sail artisan migrate

Done! Jetstream is now installed in our application. If you visit http://localhost in your browser you should see the Laravel application with two links at the top to register and log in.

welcome-page

Populate the Database and Create a User Account

Before creating a new user, let's have a quick look at the database configuration that Laravel Sail has created for us in the .env file.

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=my-example-app
DB_USERNAME=sail
DB_PASSWORD=password

As you can see, Laravel Sail configures everything we need to access the database container that is running on Docker. The DB_DATABASE is the name of the database and it is the same as the project folder. This is why in the previous step we were able to run the migrate command without issues.

Since we already migrated all database tables, we can now use the Laravel built-in user factory to create a new user then use its details to log in our user dashboard.

Let's open artisan tinker to interact with our application.

sail artisan tinker

The command above will open a command line interface that we can use to interact with our application. Let's create a new user.

User::factory()->create()

The command above will create a new user and save its data in our database. Then it will render the user data onto the screen. Make sure to copy the user email so we can use it later to log in. Then exit by typing exit;.

The default password for every user created with a factory is password.

Let's visit the login page and access our application dashboard.

loginpage

Jetstream Dashboard

After login you are redirected to the Jetstream dashboard, which looks amazing by default. We can customize it as we like, but it is just a starting point.

dashboard

Jetstream/Vue Components and Inertia Overview

The first thing you may notice after installing Jetstram is that there are a number of Vue components registered in our application. Not only that, also Inertia brings in Vue components.

To use Inertia, we need to get familiar with it when defining routes.

When we installed Jetstream, it created inside the resources/js directory a number of subfolders where all our Vue components live. There are not just simple components but also Pages components rendered by inertia as our Views.

The Jetstream inertia scaffolding created:

  • resources/js/Jetstream Here we have 27 components used by Jetstream, but we can use them in our application too if we want.
  • resources/js/Layouts In this folder there is the layout component used by inertia to render the dashboard page
  • resources/js/Pages This is where we will place all our Pages (views) components. You will find the Dashboard page as well as the Laravel Welcome page components here.

The power of Inertia mostly comes from how it connects Vue and Laravel, letting us pass data (Database Models and more) as props to our Vue Pages components.

When you open the routes/web.php file you will notice that we no longer return a view but instead we use Inertia to render a Page component.

Let's examine the / homepage route that renders the Welcome component.

Route::get('/', function () {
    return Inertia::render('Welcome', [
        'canLogin' => Route::has('login'),
        'canRegister' => Route::has('register'),
        'laravelVersion' => Application::VERSION,
        'phpVersion' => PHP_VERSION,
    ]);
});

It looks like our usual Route definition, exept that in the closure we are returning an \Inertia\Response by calling the render method of the Inertia class Inertia::render().

This method accepts two parameters. The first is a component name. Here we passed the Welcome Page component, while the second parameter is an associative array that will turn into a list of props to pass to the component. Here is where the magic happens.

Looking inside the Welcome component, you will notice that in its script section, we simply define four props matching with the keys of our associative array. Then inertia will do the rest.

<script>
    import { defineComponent } from 'vue'
    import { Head, Link } from '@inertiajs/inertia-vue3';

    export default defineComponent({
        components: {
            Head,
            Link,
        },
        // 👇 Define the props 
        props: {
            canLogin: Boolean, 
            canRegister: Boolean,
            laravelVersion: String,
            phpVersion: String,
        }
    })
</script>

We can then just call the props inside the template. If you look at the template section you will notice that laravelVersion and phpVersion are referenced in the code as you normally would do with props in Vuejs.

<div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
  Laravel v{{ laravelVersion }} (PHP v{{ phpVersion }})
</div>

The dashboard component is a little different. In fact it uses the Layout defined under Layouts/AppLayout.vue and uses the Welcome component to render the Dashboard page content, which is the same as the laravel Welcome page.


<template>
    <app-layout title="Dashboard">
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Dashboard
            </h2>
        </template>

        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                    <welcome /> 
                </div>
            </div>
        </div>
    </app-layout>
</template>

Inside the layout component you will notice the two inertia components Head and Link.

We can use the Head component to add head elements to our page, like meta tags, page title, and so on. The Link component is a wrapper aroud a standard anchor tag that incercepts click events and prevents full page reload as you can read in the Inertia documentation.

Link Component
Head Component

Disable the Registration Feature

If you are following along, the next step I'll take is to disable one on the features Jetstream provides – register an account.

To do that, we can navigate to config/fortify.php and comment out line 135 Features::registration() from the features array.

'features' => [
        //Features::registration(),
        Features::resetPasswords(),
        // Features::emailVerification(),
        Features::updateProfileInformation(),
        Features::updatePasswords(),
        Features::twoFactorAuthentication([
            'confirmPassword' => true,
        ]),
    ],

If we visit the welcome page we will notice that the register link is gone. Also, the route is no longer listed when we run sail artisan route:list.

Enable Jetstream User Profile Picture

Now let's try to enable the Jetstream feature called ProfilePhotos. As you can guess, this will allow the user to add a profile picture.

To do that we need to visit config/jetstream.php and uncomment line 59 Features::profilePhoto.

    'features' => [
        // Features::termsAndPrivacyPolicy(),
        Features::profilePhotos(), // 👈
        // Features::api(),
        // Features::teams(['invitations' => true]),
        Features::accountDeletion(),
    ],

If you log in you will see that in the user profile, a new section is available to upload a profile picture.

But before doing anything else we need to run sail artisan storage:link so that Laravel creates a symlink to the storage/app/public folder where we will save all user profile images.

Now try to visit the user profile and update the profile picture. If you get a 404 on the image this is because by default Laravel sail assumes we are using Laravel valet and sets the app URL like so APP_URL=http://my-example-app.test in the .env file. Let's change it and use localhost instead.

APP_URL=http://localhost

Now we should be good to go and be able to see and change our profile image!🥳

How to Add our First Inertia Page and Render Records from the DB

Since we are rendering Vue components instead of blade views, it is wise to start sail npm run watch to watch and recompile our Vue components as we create or edit them. Next let's add a new Photos page.

I will start by creating a new Route inside web.php:

Route::get('photos', function () {
    //dd(Photo::all());
    return Inertia::render('Guest/Photos');
});

In the code above I defined a new GET route and then rendered a component that I will place inside the resources/js/Pages/Guest and call Photos. Let's create it.

Create a Guest folder:

cd resources/js/Pages
mkdir Guest
cd Guest
touch Photos.vue

Then let's define a basic component:

<template>
  <h1>Photos Page</h1>
</template>

If we visit http://localhost/photos/ we will see our new page, cool! Let's copy over the page structure from the Welcome page so that we get the login and dashboard links as well.

The component will change to this:

<template>
    <Head title="Phots" />

    <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
        <div v-if="canLogin" class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
            <Link v-if="$page.props.user" :href="route('admin.dashboard')" class="text-sm text-gray-700 underline">
                Dashboard
            </Link>

            <template v-else>
                <Link :href="route('login')" class="text-sm text-gray-700 underline">
                    Log in
                </Link>

                <Link v-if="canRegister" :href="route('register')" class="ml-4 text-sm text-gray-700 underline">
                    Register
                </Link>
            </template>
        </div>

        <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
            <h1>Photos</h1>
            
        </div>
    </div>
</template>

<script>
    import { defineComponent } from 'vue'
    import { Head, Link } from '@inertiajs/inertia-vue3';

    export default defineComponent({
        components: {
            Head,
            Link,
        },

        props: {
            canLogin: Boolean,
            canRegister: Boolean,
          
        }
    })
</script>

The next step is to render a bunch of data onto this new page. For that we will build a Model and add some records to the database.

saild artisan make:model Photo -mfcr

This command creates a Model called Photo, plus a database migration table class, a factory, and a resource controller.

Now let's define the database table inside the migration we just creted. Visit the database/migrations folder and you should see a file with a name similar to this: 2022_02_13_215119_create_photos_table (yours will be sligly different).

Inside the migration file we can define a basic table like the following:

 public function up()
    {
        Schema::create('photos', function (Blueprint $table) {
            $table->id();
            $table->string('path');
            $table->text('description');
            $table->timestamps();
        });
    }

For our table we defined just two new columns, path and description, plus the id, created_at and updated_at that will be created by the $table->id() and by the $table->timestamps() methods.

After the migration we will define a seeder and then run the migrations and seed the database.

At the top of the database/seeders/PhotoSeeder.php file we will import our Model and Faker:

use App\Models\Photo;
use Faker\Generator as Faker;

Next we will implement the run method using a for loop to create 10 records in the database.



    public function run(Faker $faker)
    {
        for ($i = 0; $i < 10; $i++) {
            $photo = new Photo();
            $photo->path = $faker->imageUrl();
            $photo->description = $faker->paragraphs(2, true);
            $photo->save();
        }
    }

We are ready to run the migrations and seed the database.


sail artisan migrate
sail artisan db:seed --class PhotoSeeder

We are now ready to show the data on the Guest/Photos page component.
First update the route and pass a collection of Photos as props to the rendered component:

Route::get('photos', function () {
    //dd(Photo::all());
    return Inertia::render('Guest/Photos', [
        'photos' => Photo::all(), ## 👈 Pass a collection of photos, the key will become our prop in the component
        'canLogin' => Route::has('login'),
        'canRegister' => Route::has('register'),
    ]);
});

Second, pass the prop to the props in the script section of the Guest/Photos component:


props: {
    canLogin: Boolean,
    canRegister: Boolean,
    photos: Array // 👈 Here
}

Finally loop over the array and render all photos in the template section, just under the h1:

<section class="photos">
    <div v-for="photo in photos" :key="photo.id" class="card" >
        <img :src="photo.path" alt="">
    </div>
</section>

Done! if you visit the /photos page you should see ten photos. 🥳

How to Refactor the Admin Dashboard and Create New Admin Pages

In this chapter we will Re-route the Jetstream dashboard and make a route group for all admin pages.

Then we will see how to add a new link to the dashboard and add a new admin page.

Finally we will take a collection of data from the db and render them in a basic table. The default table isn't cool enough, so for those reading this article, I decided to add a Tailwind table component.

Re-route the Jetstream Dashboard

If we look at the config/fortify.php file we can see that around line 64 there is a key called home. It is calling the Home constant of the Route service provider.

This means that we can tweek the constant and redirect the authenticated user to a different route.

Lets go through it step-by-step:

  • update the HOME Constant
  • make a route group and redirect logged in users to admin/ instead of '/dashboard'

Our application will have only a single user, so once they're logged in it is clearly the site admin – so makes sense to redirect to an admin URI.

Change the HOME constant in app/Providers/RouteServiceProvider.php around line 20 to match the following:

public const HOME = '/admin';

How to Add an Admin Pages Route Group

Next let's update our route inside web.php. We will change the route registered by Jetstream from this:

Route::middleware(['auth:sanctum', 'verified'])->get('/', function () {
        return Inertia::render('Dashboard');
    })->name('dashboard');

To this:

Route::middleware(['auth:sanctum', 'verified'])->prefix('admin')->name('admin.')->group(function () {

    Route::get('/', function () {
        return Inertia::render('Dashboard');
    })->name('dashboard');

    // other admin routes here
});

The route above is a route group that uses the auth:sanctum middleware for all routes within the group, a prefix of admin, and adds a admin suffix to each route name.

The end result is that we will be able to refer to the dashboard route by name, which now will be admin.dashboard. When we log in, we will be redirected to the admin route. Our dashboard route will respond since it's URI is just / but the goup prefix will prefix every route in the group and make their URI start with admin.

If you now run sail artisan route:list you will notice that the dashboard route has changed as we expected.

Before moving to the next step we need to update both the /layouts/AppLayout.vue and /Pages/Welcome.vue components.

Do you remeber that the dashboard route name is now admin.dashboard and not just dashboard?

Let's inspect the two components and update every reference of route('dahsboard') to this:

route('admin.dahsboard')

and also every reference of route().current('dashboard') to this:

route().current('admin.dashboard')

After all the changes, make sure to recompile the Vue components and watch changes by running sail npm run watch. Then visit the home page to check if everything is working.

How to Add a New Link to the Dashboard

Now, to add a new admin page where we can list all photos stored in the database, we need to add a new route to the group we created earlier. Let's hit the web.php file and make our changes.

In the Route group we will add a new route:

Route::middleware(['auth:sanctum', 'verified'])->prefix('admin')->name('admin.')->group(function () {

    Route::get('/', function () {
        return Inertia::render('Dashboard');
    })->name('dashboard');

    // 👇 other admin routes here 👇

    Route::get('/photos', function () {
        return inertia('Admin/Photos');
    })->name('photos'); // This will respond to requests for admin/photos and have a name of admin.photos

});

In the new route above we used the inertia() helper function that does the same exact thing – returns an Inertia/Response and renders our Page component. We placed the component under an Admin folder inside Pages and we will call it Photos.vue.

Before we create the component, let's add a new link to the dashboard that points to our new route.

Inside AppLayout.vue, find the Navigation Links comment and copy/paste the jet-nav-link component that is actually displaing a link to the dashboard and make it point to our new route.

You will end up having something like this:

<!-- Navigation Links -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <jet-nav-link :href="route('admin.dashboard')" :active="route().current('admin.dashboard')">
        Dashboard
    </jet-nav-link>
    <!-- 👇 here it is our new link -->
      <jet-nav-link :href="route('admin.photos')" :active="route().current('admin.photos')">
        Photos
    </jet-nav-link>
</div>

Our link above uses route('admin.photos') to point to the correct route in the admin group.

If you visit localhost/dashboard and open the inspector, you should see an error:

Error: Cannot find module `./Photos.vue`

It is fine – we haven't created the Photos page component yet. So let's do it now!

How to Add a New Admin Page Component

Make a file named Photos.vue inside the Pages/Admin folder. Below are the bash commands to create the folder and the file via terminal, but you can do the same using your IDE's graphical interface.

cd resources/js/Pages
mkdir Admin
touch Admin/Photos.vue

To make this new page look like the Dashboard page, we will copy over its content. You should end up having something like this:


<template>
  <app-layout title="Dashboard"> <!-- 👈 if you want you can update the page title -->
    <template #header>
      <h2 class="font-semibold text-xl text-gray-800 leading-tight">Photos</h2>
    </template>

    <div class="py-12">
      <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
        <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
          <!-- 👇  All photos for the Admin page down here -->
          <h1 class="text-2xl">Photos</h1>
           
        </div>
      </div>
    </div>
  </app-layout>
</template>

<script>
import { defineComponent } from "vue";
import AppLayout from "@/Layouts/AppLayout.vue";

export default defineComponent({
  components: {
    AppLayout,
  },
});
</script>

I removed a few pieces from the Dashboard template so make sure to double check the code above. The welcome component was removed from the template as it is not required in this page, and also its reference in the script section. The rest is identical.

Feel free to update the page title referenced as prop on the <app-layout title="Dashboard">.

Now when you visit localhost/admin you can click on the Photos menu item and see our Photos page component content. It's not much for now, just an h1.

How to Render Records in the Admin Page as a Table

Now it's time to render the data onto a table. To make things work let's first add our markup and fake that we already have access to as an array of objects and loop over them inside our table. Than we will figure out how to make things work for real.

 <table class="table-auto w-full text-left">
  <thead>
    <tr>
      <th>ID</th>
      <th>Photo</th>
      <th>Desciption</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="photo in photos">
      <td>{{ photo.id }}</td>
      <td><img width="60" :src="photo.path" alt="" /></td>
      <td>{{photo.description}}</td>
      <td>View - Edit - Delete</td>

    </tr>
  </tbody>
</table>

Ok, since we assumed that our component has access to a list of Photos, let's pass a new prop to the component from the Route.

Update the route in web.php and pass to the inertia() function a second argument that will be an associative array. It will have its keys passed as props to the Vue Page component.

In it we will call Photo::all() to have a collection to assign to a photos key, but you can use other eloquent methods if you want to paginate the results, for example.

Route::get('/photos', function () {
    return inertia('Admin/Photos', [
        'photos' => Photo::all()
    ]);
})->name('photos');

To connect the prop to our Page component we need to define the prop also inside the component.

<script>
import { defineComponent } from "vue";
import AppLayout from "@/Layouts/AppLayout.vue";

export default defineComponent({
  components: {
    AppLayout,
  },
  /* 👇 Pass the photos array as a props 👇 */
  props: {
    photos: Array,
  },
});
</script>

Extra: How to use a Tailwind table component

Tailwind is a CSS framework similar to Bootstrap. There are a number of free to use components that we can grab from the documentation, tweak, and use.

This table component is free and looks nice:https://tailwindui.com/components/application-ui/lists/tables.

We can tweek the Photos page template and use the Tailwind table component to get a nice looking table like so:


<template>
    <app-layout title="Dashboard">
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">Photos</h2>
        </template>

         <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
              <!-- All posts goes here -->
              <h1 class="text-2xl">Photos</h1>
              <a class="px-4 bg-sky-900 text-white rounded-md" href>Create</a>
              <div class="flex flex-col">
                  <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
                      <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                          <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
                              <table class="min-w-full divide-y divide-gray-200">
                                  <thead class="bg-gray-50">
                                      <tr>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >ID</th>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >Photos</th>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >Description</th>
                                          <th scope="col" class="relative px-6 py-3">
                                              <span class="sr-only">Edit</span>
                                          </th>
                                      </tr>
                                  </thead>
                                  <tbody class="bg-white divide-y divide-gray-200">
                                      <tr v-for="photo in photos" :key="photo.id">
                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div
                                                  class="text-sm text-gray-900"
                                              >{{ photo.id }}</div>
                                          </td>

                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div class="flex items-center">
                                                  <div class="flex-shrink-0 h-10 w-10">
                                                      <img
                                                          class="h-10 w-10 rounded-full"
                                                          :src="photo.path"
                                                          alt
                                                      />
                                                  </div>
                                              </div>
                                          </td>

                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div class="text-sm text-gray-900">
                                                {{ photo.description.slice(0, 100) + '...' }}
                                              </div>
                                          </td>
                                        <!-- ACTIONS -->
                                          <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                              <a href="#" class="text-indigo-600 hover:text-indigo-900">
                                              View - Edit - Delete
                                              </a>
                                          </td>
                                      </tr>
                                  </tbody>
                              </table>
                          </div>
                      </div>
                  </div>
                </div>
            </div>
        </div>
    </app-layout>
</template>

How to Submit Forms with Files

For the next section we will look into how to submit a form so that we can add a new photo to the database.

  • Add a create button
  • Add a create route
  • Define the PhotosCreate component
  • Add a form
  • Validate data
  • Show validation errors
  • Save the file to the filesystem
  • Save the model

How to Create a New Photo

Add a link that points to a create route:

<a class="px-4 bg-sky-900 text-white rounded-md" :href="route('admin.photos.create')">Create</a>

Create the route within the admin group:

Route::get('/photos/create', function () {
    return inertia('Admin/PhotosCreate');
})->name('photos.create');

Let's add also the route that will handle the form submission for later:

Route::post('/photos', function () {
    dd('I will handle the form submission')   
})->name('photos.store');

Create the Admin/PhotosCreate.vue component:


    <template>
    <app-layout title="Dashboard">
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">Photos</h2>
        </template>

         <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <h1 class="text-2xl">Add a new Photo</h1>
                <!-- 👇 Photo creation form goes here -->

            </div>
        </div>
    </app-layout>
</template>


<script>
import { defineComponent } from "vue";
import AppLayout from "@/Layouts/AppLayout.vue";

export default defineComponent({
  components: {
    AppLayout,
  },

});
</script>

How to Add the Form to the Component

The next step is to add the form to the page and figure out how to submit it.

If you hit the Inertia documentation you will find out that there is a useForm class that we can use to simplify the process.

First, import the module inside the script tag of the Admin/PhotosCreate.vue component:

import { useForm } from '@inertiajs/inertia-vue3';

Next we can use it in the setup function (Vue 3 composition API):

setup () {
    const form = useForm({
      path: null,
      description: null,
    })

    return { form }
  }

In the code above we defined the function called setup() then a constant called form to have the useForm() class assigned to it.

Inside its parentheses we defined two properties, path and description which are the column names of our photos model.

Finally we returned the form variable for the setup function. This is to make the variable available inside our template.

Next we can add the form markup:

<form @submit.prevent="form.post(route('admin.photos.store'))">

<div>
    <label for="description" class="block text-sm font-medium text-gray-700"> Description </label>
    <div class="mt-1">
        <textarea id="description" name="description" rows="3" class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md" placeholder="lorem ipsum" v-model="form.description"/>
    </div>
    <p class="mt-2 text-sm text-gray-500">Brief description for your photo</p>
        <div class="text-red-500" v-if="form.errors.description">{{form.errors.description}}</div>
</div>
<div>
    <label class="block text-sm font-medium text-gray-700"> Photo </label>
    <div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
    <div class="space-y-1 text-center">
        <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
        <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
        </svg>
        <div class="flex text-sm text-gray-600">
        <label for="path" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
            <span>Upload a file</span>
            <input id="path" name="path" type="file" class="sr-only" @input="form.path = $event.target.files[0]" />
        </label>
        <p class="pl-1">or drag and drop</p>
        </div>
        <p class="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
    </div>
    </div>
</div>
<div class="text-red-500" v-if="form.errors.path">{{form.errors.path}}</div>

<button type="submit" :disabled="form.processing" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save</button>
</form>

The code above uses the Vue v-on directive short end syntax @submit.prevent="form.post(route('admin.photos.store'))" on the form tag, and the dom event submit with the prevent modifier.

Then it uses the form variable that we created earlier and a post method. This is available because we are using the useForm class.

Next we point the form to the route named admin.photos.store that we created earlier.

Inside the form we have two groups of inputs. First, we have the textarea that uses the v-model to bind it to the property form.description that we declared before.

The second group uses the form.path in a Tailwind component (showing the markup for a drop file area).

Right now we are allowing users to upload only a single photo using the v-on directive on the input DOM event @input="form.path = $event.target.files[0]".

The last two things to notice are the error handling done via <div class="text-red-500" v-if="form.errors.path">{{form.errors.path}}</div> for the path and also for the description.

Finally we use form.processing to disable the submit button while the form is processing.

The next step is to define the logic to save the data inside the database.

How to Store Data

To store the data, we can edit the route we defined earlier like so:

Route::post('/photos', function (Request $request) {
    //dd('I will handle the form submission')  
    
    //dd(Request::all());
    $validated_data = $request->validate([
        'path' => ['required', 'image', 'max:2500'],
        'description' => ['required']
    ]);
    //dd($validated_data);
    $path = Storage::disk('public')->put('photos', $request->file('path'));
    $validated_data['path'] = $path;
    //dd($validated_data);
    Photo::create($validated_data);
    return to_route('admin.photos');
})->name('photos.store');

The code above uses dependency injection to allow us to use the parameter $request inside the callback function.

We first validate the request and save the resulting array inside the variable $validated_data. Then we use the Storage facades to save the file in the filesystem and obtain the file path that we store inside the $path variable.

Finally we add a path key to the associative array and pass to it the $path variable. Next we create the resource in the database using the Photo::create method and redirect the user to the admin.photos page using the new to_route() helper function.

Make sure to import the Request class and the Storage facades at the top of the web.php file like so:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

Now we can add a new photo in the database and show a list of photos for both the admin and standard visitors.

Next we need to complete the CRUD operations and allow the user to edit/update a photo and delete it.

How to Update Operations

Let's start by adding the routes responsible for showing the forms used to edit the resource and update its values onto the database.

Just under the other routes in the Admin group, let's add the following code:


Route::get('/photos/{photo}/edit', function(Photo $photo){
     return inertia('Admin/PhotosEdit', [
            'photo' => $photo
        ]);
})->name('photos.edit');

The route above uses dependency injection to inject inside the function the current post, selected by the URI /photos/{photo}/edit.

Next it returns the Inertia response via the inertia() function that accepts the Component name 'Admin/PhotosEdit' as its first parameter and an associative array as its second.

Doing ['photo' => $photo] will allow us to pass the $photo model as a prop to the component later.

Next let's add the new Page component under resources/js/Pages/Admin/PhotosEdit.vue

This will be its template:

<template>
    <app-layout title="Edit Photo">
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">Edit Photo</h2>
        </template>
        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <form @submit.prevent="form.post(route('admin.photos.update', photo.id))">
                    <div>
                        <label
                            for="description"
                            class="block text-sm font-medium text-gray-700"
                        >Description</label>
                        <div class="mt-1">
                            <textarea
                                id="description"
                                name="description"
                                rows="3"
                                class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
                                placeholder="lorem ipsum"
                                v-model="form.description"
                            />
                        </div>
                        <p class="mt-2 text-sm text-gray-500">Brief description for your photo</p>
                        <div
                            class="text-red-500"
                            v-if="form.errors.description"
                        >{{ form.errors.description }}</div>
                    </div>

                    <div class="grid grid-cols-2">
                        <div class="preview p-4">
                            <img :src="'/storage/' + photo.path" alt />
                        </div>
                        <div>
                            <label class="block text-sm font-medium text-gray-700">Photo</label>
                            <div
                                class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md"
                            >
                                <div class="space-y-1 text-center">
                                    <svg
                                        class="mx-auto h-12 w-12 text-gray-400"
                                        stroke="currentColor"
                                        fill="none"
                                        viewBox="0 0 48 48"
                                        aria-hidden="true"
                                    >
                                        <path
                                            d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
                                            stroke-width="2"
                                            stroke-linecap="round"
                                            stroke-linejoin="round"
                                        />
                                    </svg>
                                    <div class="flex text-sm text-gray-600">
                                        <label
                                            for="path"
                                            class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
                                        >
                                            <span>Upload a file</span>
                                            <input
                                                id="path"
                                                name="path"
                                                type="file"
                                                class="sr-only"
                                                @input="form.path = $event.target.files[0]"
                                            />
                                        </label>
                                        <p class="pl-1">or drag and drop</p>
                                    </div>
                                    <p class="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
                                </div>
                            </div>
                            <div class="text-red-500" v-if="form.errors.path">{{ form.errors.path }}</div>
                        </div>
                    </div>

                    <button
                        type="submit"
                        :disabled="form.processing"
                        class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                    >Update</button>
                </form>
            </div>
        </div>
    </app-layout>
</template>

The template is actually identical to the Create component, except for a few things. The form points to a route that expects a paramenter that we pass as the second argument to the funtion route. It looks like this: <form @submit.prevent="form.post(route('admin.photos.update', photo.id))">.

There is a section where we can see the original photo next to the upload form group:

 <div class="preview p-4">
    <img :src="'/storage/' + photo.path" alt />
</div>

The rest is identical, and here we have the script section:

import { defineComponent } from "vue";
import AppLayout from "@/Layouts/AppLayout.vue";
import { useForm } from '@inertiajs/inertia-vue3';

export default defineComponent({
    components: {
        AppLayout,
    },
    props: {
        photo: Object
    },
    setup(props) {
        const form = useForm({
            _method: "PUT",
            path: null,
            description: props.photo.description,
        })

        return { form }
    },

});

Notice that we are passing a props object with the photo key, which allows us to reference the model in the template.

Next, this _method: "PUT", line of code is required to be able to submit a PUT request instead of the POST request called on the form tag.

Now let's implement the logic to handle the form submission inside the Route below.

In web.php just under the previous route, let's add one that responds to the PUT request submitted by our form.

Route::put('/photos/{photo}', function (Request $request, Photo $photo)
    {
        //dd(Request::all());

        $validated_data = $request->validate([
            'description' => ['required']
        ]);

        if ($request->hasFile('path')) {
            $validated_data['path'] = $request->validate([
                'path' => ['required', 'image', 'max:1500'],

            ]);

            // Grab the old image and delete it
            // dd($validated_data, $photo->path);
            $oldImage = $photo->path;
            Storage::delete($oldImage);

            $path = Storage::disk('public')->put('photos', $request->file('path'));
            $validated_data['path'] = $path;
        }

        //dd($validated_data);

        $photo->update($validated_data);
        return to_route('admin.photos');
    })->name('photos.update');


The route logic is straigthforward. First we validate the description, next we check if a file was uploaded and if so we validate it.

Then we delete the previously uploaded image Storage::delete($oldImage); before storing the new image onto the datadabse and update the resource using $photo->update($validated_data);.

As before with the store route, we redirect to the admin.photos route using return to_route('admin.photos');.

How to Delete a Resource

The last step we need to take is to write the logic to delete the photo. Let's start by adding the route.

Right below the previous route we can write:

Route::delete('/photos/{photo}', function (Photo $photo)
{
    Storage::delete($photo->path);
    $photo->delete();
    return to_route('admin.photos');
})->name('photos.delete');

This route is also using a wildcard in its URI to identify the resource. Next, its second paramenter is the callback that uses the dependency injection as before. Inside the callback we first delete the image from the filesystem using Storage::delete($photo->path);.

Then we remove the resource from the database $photo->delete(); and redirect the user back return to_route('admin.photos'); like we did in the previous reoute.

Now we need to add a delete button to the table we created in one of the previous steps to show all photos.

Inside the template section of the component Admin/Photos.vue within the v-for, we can add this Jetstream button:


<jet-danger-button @click="delete_photo(photo)">
    Delete
</jet-danger-button>

Find the table cell that has the ACTIONS comment and replace the DELETE text with the button above.

So the final code will be:

<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
    <a href="#" class="text-indigo-600 hover:text-indigo-900">
    View - Edit - 

    <jet-danger-button @click="delete_photo(photo)">
        Delete
    </jet-danger-button>
    </a>
</td>

As you can see there is a @click event listener on the button. It calls a method delete_photo(photo) that we need to define along with a bunch of other methods to have a nice modal opening to ask for confirmation from the user.

First import the Inertia helper function useForm:

// 0. Import the useForm class at the top of the script section along with all required components
import { useForm } from '@inertiajs/inertia-vue3';
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import { ref } from "vue";

Remember to register the component JetDangerButton inside the components object before moving forward.

Next add the setup() function in the script section and implement the logic required to submit the form and show a modal. The comments in the code will guide you thorought all the steps.

// 1. add the setup function
setup() {
    // 2. declare a form variable and assign to it the Inertia useForm() helper function 
    const form = useForm({
        // 3. override the form method to make a DELETE request
        _method: "DELETE",
    });
    // 4. define a reactive object with show_modal and photo property
    // this will be used to figure out when to show the modal and the selected post values
    const data = ref({
        show_modal: false,
        photo: {
            id: null,
            path: null,
            description: null,
        }
    })

    // 5. define the delete_photo function and update the values of the show_modal and photo properties
    // of the reactive object defined above. This method is called by the delete button and will record the details 
    // of the selected post
    const delete_photo = (photo) => {
        //console.log(photo);
        //console.log(photo.id, photo.path, photo.description);
        data.value = {
            photo: {
                id: photo.id,
                path: photo.path,
                description: photo.description
            },
            show_modal: true
        };
    }
    // 6. define the method that will be called when our delete form is submitted
    // the form will be created next
    const deleting_photo = (id) => {
        form.post(route('admin.photos.delete', id))
        closeModal();
    }
    // 7. delare a method to close the modal by setting the show_modal to false
    const closeModal = () => {
        data.value.show_modal = false;
    }
    // 8. remember to return from the setup function the all variables and methods that you want to expose 
    // to the template.
    return { form, data, closeModal, delete_photo, deleting_photo }

}

Finally outside the v-for loop add the modal using the following code. You can place this where you want but not inside the loop.


 <JetDialogModal :show="data.show_modal">
    <template #title>
        Photo {{ data.photo.description.slice(0, 20) + '...' }}
    </template>
    <template #content>
        Are you sure you want to delete this photo?

    </template>
    <template #footer>
        <button @click="closeModal" class="px-4 py-2">Close</button>
        <form @submit.prevent="deleting_photo(data.photo.id)">
            <jet-danger-button type="submit">Yes, I am sure!</jet-danger-button>
        </form>
    </template>
</JetDialogModal>

This is our final JavaScript code:

import { defineComponent } from "vue";
import AppLayout from "@/Layouts/AppLayout.vue";
import TableComponent from "@/Components/TableComponent.vue";
import { Link } from '@inertiajs/inertia-vue3';
import { useForm } from '@inertiajs/inertia-vue3';
import JetDialogModal from '@/Jetstream/DialogModal.vue';
import JetDangerButton from '@/Jetstream/DangerButton.vue'
import { ref } from "vue";
export default defineComponent({
    components: {
        AppLayout,
        Link,
        TableComponent,
        JetDialogModal,
        JetDangerButton
    },
    props: {
        photos: Array,
    },

    setup() {

        const form = useForm({
            _method: "DELETE",
        });
        const data = ref({
            show_modal: false,
            photo: {
                id: null,
                path: null,
                description: null,
            }

        })


        const delete_photo = (photo) => {
            //console.log(photo);
            console.log(photo.id, photo.path, photo.description);
            data.value = {
                photo: {
                    id: photo.id,
                    path: photo.path,
                    description: photo.description
                },
                show_modal: true
            };
        }
        const deleting_photo = (id) => {
            form.post(route('admin.photos.delete', id))
            closeModal();
        }

        const closeModal = () => {
            data.value.show_modal = false;


        }

        return { form, data, closeModal, delete_photo, deleting_photo }

    }
});
</script>

And here we have the HTML:

<template>
    <app-layout title="Dashboard">
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">Photos</h2>
        </template>

         <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
              <!-- All posts goes here -->
              <h1 class="text-2xl">Photos</h1>
              <a class="px-4 bg-sky-900 text-white rounded-md" href>Create</a>
              <div class="flex flex-col">
                  <div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
                      <div class="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                          <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg">
                              <table class="min-w-full divide-y divide-gray-200">
                                  <thead class="bg-gray-50">
                                      <tr>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >ID</th>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >Photos</th>
                                          <th
                                              scope="col"
                                              class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
                                          >Description</th>
                                          <th scope="col" class="relative px-6 py-3">
                                              <span class="sr-only">Edit</span>
                                          </th>
                                      </tr>
                                  </thead>
                                  <tbody class="bg-white divide-y divide-gray-200">
                                      <tr v-for="photo in photos" :key="photo.id">
                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div
                                                  class="text-sm text-gray-900"
                                              >{{ photo.id }}</div>
                                          </td>

                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div class="flex items-center">
                                                  <div class="flex-shrink-0 h-10 w-10">
                                                      <img
                                                          class="h-10 w-10 rounded-full"
                                                          :src="photo.path"
                                                          alt
                                                      />
                                                  </div>
                                              </div>
                                          </td>

                                          <td class="px-6 py-4 whitespace-nowrap">
                                              <div class="text-sm text-gray-900">
                                                {{ photo.description.slice(0, 100) + '...' }}
                                              </div>
                                          </td>
                                        <!-- ACTIONS -->
                                         <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                            <a href="#" class="text-indigo-600 hover:text-indigo-900">
                                            View - Edit - 

                                            <jet-danger-button @click="delete_photo(photo)">
                                                Delete
                                            </jet-danger-button>
                                            </a>
                                        </td>
                                      </tr>
                                  </tbody>
                              </table>
                          </div>
                      </div>
                  </div>
                </div>
            </div>
        </div>
         <JetDialogModal :show="data.show_modal">
            <template #title>
                Photo {{ data.photo.description.slice(0, 20) + '...' }}
            </template>
            <template #content>
                Are you sure you want to delete this photo?

            </template>
            <template #footer>
                <button @click="closeModal" class="px-4 py-2">Close</button>
                <form @submit.prevent="deleting_photo(data.photo.id)">
                    <jet-danger-button type="submit">Yes, I am sure!</jet-danger-button>
                </form>
            </template>
        </JetDialogModal>
    </app-layout>
</template>

That's it. If you did everything correctly you should be able to see all photos, create new photos as well as edit and delete them.

I will leave you some home work. Can you figure out how to implement the view and edit links before the delete button in the section below?

<!-- ACTIONS -->
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
    <a href="#" class="text-indigo-600 hover:text-indigo-900">
    View - Edit - 

    <jet-danger-button @click="delete_photo(photo)">
        Delete
    </jet-danger-button>
    </a>
</td>

Wrapup and What's next

During this guide we took our first steps and learned how to build a single page application using Laravel as our backend framework and Vue3 for the front end. We glued them together with Inertia js and built a simple photo application that lets a user manage photos.

We are just at the beginning of a fantastic journey. Learning new technologies isn't easy, but thanks to their exaustive documentations we can keep up and build awesome applications.

Your next step to master Laravel, Vue3, Inertia and all the tech we have been using so far is to hit their documentation and keep learning. Use the app we have build if you want, and improve it or start over from scratch.

Conclusion

This is just an overview of how I'd build a single page application using these technologies.

If you are familiar with server-side routing and Vuejs then you will enjoy bulding a single page application with Laravel, Inertia, and Vuejs. The learning curve isn't that steep plus you have great documentation to help you out.

You can find the source code for this guide here.

#laravel #laravel9 #jetstream #vuejs #inertiajs #mysql #tailwindcss #docker
 

Ruthie  Bugala

Ruthie Bugala

1651204800

Nbind: Magical Headers That Make Your C++ Library Accessible From JS

nbind is a set of headers that make your C++11 library accessible from JavaScript. With a single #include statement, your C++ compiler generates the necessary bindings without any additional tools. Your library is then usable as a Node.js addon or, if compiled to asm.js with Emscripten, directly in web pages without any plugins.

nbind works with the autogypi dependency management tool, which sets up node-gyp to compile your library without needing any configuration (other than listing your source code file names).

nbind is MIT licensed and based on templates and macros inspired by embind.

Quick start

C++ everywhere in 5 easy steps using Node.js, nbind and autogypi:

Starting pointStep 1 - bindStep 2 - prepare
Original C++ code hello.cc:
#include <string> #include <iostream>   struct Greeter {  static void sayHello(    std::string name  ) {    std::cout      << "Hello, "      << name << "!\n";  } };
List your classes and methods:
// Your original code here   // Add these below it:   #include "nbind/nbind.h"   NBIND_CLASS(Greeter) {  method(sayHello); }
Add scripts to package.json:
{  "scripts": {    "autogypi": "autogypi",    "node-gyp": "node-gyp",    "emcc-path": "emcc-path",    "copyasm": "copyasm",    "ndts": "ndts"  } }
Step 3 - installStep 4 - buildStep 5 - use!
Run on the command line:
npm install --save \  nbind autogypi node-gyp   npm run -- autogypi \  --init-gyp \  -p nbind -s hello.cc
Compile to native binary:
npm run -- node-gyp \  configure buildOr to Asm.js:
npm run -- node-gyp \  configure build \  --asmjs=1
Call from Node.js:
var nbind = require('nbind'); var lib = nbind.init().lib;   lib.Greeter.sayHello('you');Or from a web browser (see below).

The above is all of the required code. Just copy and paste in the mentioned files and prompts or take a shortcut:

git clone https://github.com/charto/nbind-example-minimal.git
cd nbind-example-minimal
npm install && npm test

See it run!

(Note: nbind-example-universal is a better starting point for development)

Requirements

You need:

And one of the following C++ compilers:

  • GCC 4.8 or above.
  • Clang 3.6 or above.
  • Emscripten 1.35.0 or above.
  • Visual Studio 2015 (the Community version is fine).

Features

nbind allows you to:

  • Use your C++ API from JavaScript without any extra effort.
    • From Node.js, Electron and web browsers (using asm.js on Chrome, Firefox and Edge).
    • On Linux, OS X and Windows.
    • Without changes to your C++ code. Simply add a separate short description at the end.
  • Distribute both native code and an asm.js fallback binary.
  • Automatically generate TypeScript .d.ts definition files from C++ code for IDE autocompletion and compile-time checks of JavaScript side code.

In more detail:

  • Export multiple C++ classes, even ones not visible from other files.
  • Export C++ methods simply by mentioning their names.
  • Auto-detect argument and return types from C++ declarations.
  • Automatically convert types and data structures between languages.
  • Call C++ methods from JavaScript with type checking.
  • Pass JavaScript callbacks to C++ and call them with any types.
  • Pass instances of compatible classes by value between languages (through the C++ stack).

The goal is to provide a stable API for binding C++ to JavaScript. All internals related to JavaScript engines are hidden away, and a single API already supports extremely different platforms.

Works on your platform

TargetDevelopment platform
 Linux / OS XWindows
NativeBuild status Build status 
Asm.jsBuild status Tested manually

Roadmap

More is coming! Work is ongoing to:

  • Precompile to a single native library for all versions Node.js and Electron on the same platform
    • Precompiled addons for different Node.js versions for efficiently calling the library will be provided with nbind
  • Support native Android and iPhone apps.

Future 0.x.y versions should remain completely backwards-compatible between matching x and otherwise with minor changes. Breaking changes will be listed in release notes of versions where y equals 0.

Contributing

Please report issues through Github and mention the platform you're targeting (Node.js, asm.js, Electron or something else). Pull requests are very welcome.

Warning: rebase is used within develop and feature branches (but not master).

When developing new features, writing tests first works best. If possible, please try to get them working on both Node.js and asm.js. Otherwise your pull request will get merged to Master only after maintainer(s) have fixed the other platform.

Installing Emscripten to develop for asm.js can be tricky. It will require Python 2.7 and setting paths correctly, please refer to Emscripten documentation. The bin/emcc script in this package is just a wrapper, the actual emcc compiler binary should be in your path.

You can rebuild the asm.js library and run tests as follows:

npm run clean-asm && npm run prepublish && npm run test-asm

User guide

Installing the examples

nbind examples shown in this user guide are also available to download for easier testing as follows:

Extract this zip package or run:

git clone https://github.com/charto/nbind-examples.git

Enter the examples directory and install:

cd nbind-examples
npm install

Creating your project

Once you have all requirements installed, run:

npm init
npm install --save nbind autogypi node-gyp

nbind, autogypi and node-gyp are all needed to compile a native Node.js addon from source when installing it. If you only distribute an asm.js version, you can use --save-dev instead of --save because users won't need to compile it.

Next, to run commands without installing them globally, it's practical to add them in the scripts section of your package.json that npm init just generated. Let's add an install script as well:

  "scripts": {
    "autogypi": "autogypi",
    "node-gyp": "node-gyp",
    "emcc-path": "emcc-path",
    "copyasm": "copyasm",

    "install": "autogypi && node-gyp configure build"
  }

emcc-path is needed internally by nbind when compiling for asm.js. It fixes some command line options that node-gypi generates on OS X and the Emscripten compiler doesn't like. You can leave it out if only compiling native addons.

The install script runs when anyone installs your package. It calls autogypi and then uses node-gyp to compile a native addon.

autogypi uses npm package information to set correct include paths for C/C++ compilers. It's needed when distributing addons on npm so the compiler can find header files from the nbind and nan packages installed on the user's machine. Initialize it like this:

npm run -- autogypi --init-gyp -p nbind -s hello.cc

Replace hello.cc with the name of your C++ source file. You can add multiple -s options, one for each source file.

The -p nbind means the C++ code uses nbind. Multiple -p options can be added to add any other packages compatible with autogypi.

The --init-gyp command generates files binding.gyp and autogypi.json that you should distribute with your package, so that autogypi and node-gyp will know what to do when the install script runs.

Now you're ready to start writing code and compiling.

Configuration

Refer to autogypi documentation to set up dependencies of your package, and how other packages should include it if it's a library usable directly from C++.

--asmjs=1 is the only existing configuration option for nbind itself. You pass it to node-gyp by calling it like node-gyp configure build --asmjs=1. It compiles your package using Emscripten instead of your default C++ compiler and produces asm.js output.

Calling from Node.js

First nbind needs to be initialized by calling nbind.init which takes the following optional arguments:

  • Base path under which to look for compiled binaries. Default is process.cwd() and __dirname is a good alternative.
  • Binary code exports object. Any classes from C++ API exported using nbind will be added as members. Default is an empty object. Any existing options will be seen by asm.js code and can be used to configure Emscripten output. Must follow base path (which may be set to null or undefined).
  • Node-style callback with 2 parameters:
    • Error if present, otherwise null.
    • Binary code exports object containing C++ classes.

nbind can be initialized synchronously on Node.js and asynchronously on browsers and Node.js. Purely synchronous is easier but not as future-proof:

var nbind = require('nbind');
var lib = nbind.init().lib;

// Use the library.

Using a callback also supports asynchronous initialization:

var nbind = require('nbind');

nbind.init(function(err, binding) {
  var lib = binding.lib;

  // Use the library.
});

The callback passed to init currently gets called synchronously in Node.js and asynchronously in browsers. To avoid releasing zalgo you can for example wrap the call in a bluebird promise:

var bluebird = require('bluebird');
var nbind = require('nbind');

bluebird.promisify(nbind.init)().then(function(binding) {
  var lib = binding.lib;

  // Use the library.
});

Using nbind headers

There are two possible files to include:

  • nbind/api.h for using types from the nbind namespace such as JavaScript callbacks inside your C++ code.
    • #include before your own class definitions.
    • Causes your code to depend on nbind.
  • nbind/nbind.h for exposing your C++ API to JavaScript.
    • #include after your own class definitions to avoid accidentally invoking its macros.
    • The header automatically hides itself if not targeting Node.js or asm.js.
    • Safe to use in any projects.

Use #include "nbind/nbind.h" at the end of your source file with only the bindings after it. The header defines macros with names like construct and method that may otherwise break your code or conflict with other headers.

It's OK to include nbind/nbind.h also when not targeting any JavaScript environment. node-gyp defines a BUILDING_NODE_EXTENSION macro and Emscripten defines an EMSCRIPTEN macro so when those are undefined, the include file does nothing.

Use #include "nbind/api.h" in your header files to use types in the nbind namespace if you need to report errors without throwing exceptions, or want to pass around callbacks or objects.

You can use an #ifdef NBIND_CLASS guard to skip your nbind export definitions when the headers weren't loaded.

Example that uses an nbind callback in C++ code:

1-headers.cc

#include <string>
#include <iostream>

// For nbind::cbFunction type.
#include "nbind/api.h"

class HeaderExample {

public:

  static void callJS(nbind::cbFunction &callback) {
    std::cout << "JS says: " << callback.call<std::string>(1, 2, 3);
  }

};

// For NBIND_CLASS() and method() macros.
#include "nbind/nbind.h"

#ifdef NBIND_CLASS

NBIND_CLASS(HeaderExample) {
  method(callJS);
}

#endif

Example used from JavaScript:

1-headers.js

var nbind = require('nbind');

var lib = nbind.init().lib;

lib.HeaderExample.callJS(function(a, b, c) {
  return('sum = ' + (a + b + c) + '\n');
});

Run the example with node 1-headers.js after installing. It prints:

JS says: sum = 6

Functions

Functions not belonging to any class are exported inside an NBIND_GLOBAL block with a macro call function(functionName); which takes the name of the function as an argument (without any quotation marks). The C++ function gets exported to JavaScript with the same name, or it can be renamed by adding a second argument (with quotation marks): function(cppFunctionName, "jsExportedName");

If the C++ function is overloaded, multifunction macro must be used instead. See overloaded functions.

Note: you cannot put several function(...); calls on the same line! Otherwise you'll get an error about redefining a symbol.

Example:

6-functions.cc

#include <iostream>

void sayHello(std::string name) {
  std::cout << "Hello, " << name << "!\n";
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(sayHello);
}

Example used from JavaScript:

6-functions.js

var nbind = require('nbind');
var lib = nbind.init().lib;

lib.sayHello('you');

Classes and constructors

The NBIND_CLASS(className) macro takes the name of your C++ class as an argument (without any quotation marks), and exports it to JavaScript using the same name. It's followed by a curly brace enclosed block of method exports, as if it was a function definition.

The class can be renamed on the JavaScript side by passing a string as a second argument. This is especially useful for binding a template class specialization with a more reasonable name: NBIND_CLASS(Data<int>, "IntData")

Constructors are exported with a macro call construct<types...>(); where types is a comma-separated list of arguments to the constructor, such as int, int. Calling construct multiple times allows overloading it, but each overload must have a different number of arguments.

Constructor arguments are the only types that nbind cannot detect automatically.

Example with different constructor argument counts and types:

2-classes.cc

#include <iostream>

class ClassExample {

public:

  ClassExample() {
    std::cout << "No arguments\n";
  }
  ClassExample(int a, int b) {
    std::cout << "Ints: " << a << " " << b << "\n";
  }
  ClassExample(const char *msg) {
    std::cout << "String: " << msg << "\n";
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(ClassExample) {
  construct<>();
  construct<int, int>();
  construct<const char *>();
}

Example used from JavaScript:

2-classes.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var a = new lib.ClassExample();
var b = new lib.ClassExample(42, 54);
var c = new lib.ClassExample("Don't panic");

Run the example with node 2-classes.js after installing. It prints:

No arguments
Ints: 42 54
String: Don't panic

Inheritance

When a C++ class inherits another, the inherit macro can be used to allow calling parent class methods on the child class, or passing child class instances to C++ methods expecting parent class instances.

Internally JavaScript only has prototype-based single inheritance while C++ supports multiple inheritance. To simulate it, nbind will use one parent class as the child class prototype, and copy the contents of the other parents to the prototype. This has otherwise the same effect, except the JavaScript instanceof operator will return true for only one of the parent classes.

Example:

NBIND_CLASS(Child) {
    inherit(FirstParent);
    inherit(SecondParent);
}

Methods and properties

Methods are exported inside an NBIND_CLASS block with a macro call method(methodName); which takes the name of the method as an argument (without any quotation marks). The C++ method gets exported to JavaScript with the same name.

If the C++ method is overloaded, multimethod macro must be used instead. See overloaded functions.

Properties should be accessed through getter and setter functions.

Data types of method arguments and its return value are detected automatically so you don't have to specify them. Note the supported data types because using other types may cause compiler errors that are difficult to understand.

If the method is static, it becomes a property of the JavaScript constructor function and can be accessed like className.methodName(). Otherwise it becomes a property of the prototype and can be accessed like obj = new className(); obj.methodName();

Example with a method that counts a cumulative checksum of ASCII character values in strings, and a static method that processes an entire array of strings:

3-methods.cc

#include <string>
#include <vector>

class MethodExample {

public:

  unsigned int add(std::string part) {
    for(char &c : part) sum += c;

    return(sum);
  }

  static std::vector<unsigned int> check(std::vector<std::string> list) {
    std::vector<unsigned int> result;
    MethodExample example;

    for(auto &&part : list) result.push_back(example.add(part));

    return(result);
  }

  unsigned int sum = 0;

};

#include "nbind/nbind.h"

NBIND_CLASS(MethodExample) {
  construct<>();

  method(add);
  method(check);
}

Example used from JavaScript, first calling a method in a loop from JS and then a static method returning an array:

3-methods.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var parts = ['foo', 'bar', 'quux'];

var checker = new lib.MethodExample();

console.log(parts.map(function(part) {
  return(checker.add(part));
}));

console.log(lib.MethodExample.check(parts));

Run the example with node 3-methods.js after installing. It prints:

[ 324, 633, 1100 ]
[ 324, 633, 1100 ]

The example serves to illustrate passing data. In practice, such simple calculations are faster to do in JavaScript rather than calling across languages because copying data is quite expensive.

Overloaded functions

The function() and method() macroes cannot distinguish between several overloaded versions of the same function or method, causing an error. In this case the multifunction() and multimethod() macroes must be used.

Their second parameter is a list of argument types wrapped in an args() macro to select a single overloaded version.

For example consider an overloaded method:

void test(unsigned int x) const;
void test(unsigned int x, unsigned int y) const;

In bindings, one of the versions needs to be explicitly selected. The second of the two would be referenced like:

multimethod(test, args(unsigned int, unsigned int));

As always, the return type and method constness are autodetected.

For calling from JavaScript, additionally each overload needs to have a distinct name. For renaming an overload JavaScript will see, the binding code is like:

multimethod(test, args(unsigned int, unsigned int), "test2");

You can then write a JavaScript wrapper to inspect arguments and select which overload to call. The reason for this is, that nbind binds a JavaScript property to a single C++ function pointer, which wraps one overloaded version of the function with type conversion code.

Otherwise, it would need to generate a new C++ function that also checks the arguments. This would result in a larger native binary without any speed advantage.

Getters and setters

Property getters are exported inside an NBIND_CLASS block with a macro call getter(getterName) with the name of the getter method as an argument. nbind automatically strips a get/Get/get_/Get_ prefix and converts the next letter to lowercase, so for example getX and get_x both would become getters of x to be accessed like obj.x

Property setters are exported together with getters using a macro call getset(getterName, setterName) which works much like getter(getterName) above. Both getterName and setterName are mangled individually so you can pair getX with set_x if you like. From JavaScript, ++obj.x would then call both of them to read and change the property.

Example class and property with a getter and setter:

4-getset.cc

class GetSetExample {

public:

  void setValue(int value) { this->value = value; }
  int getValue() { return(value); }

private:

  int value = 42;

};

#include "nbind/nbind.h"

NBIND_CLASS(GetSetExample) {
  construct<>();

  getset(getValue, setValue);
}

Example used from JavaScript:

4-getset.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var obj = new lib.GetSetExample();

console.log(obj.value++); // 42
console.log(obj.value++); // 43

Run the example with node 4-getset.js after installing.

Passing data structures

nbind supports automatically converting between JavaScript arrays and C++ std::vector or std::array types. Just use them as arguments or return values in C++ methods.

Note that data structures don't use the same memory layout in both languages, so the data always gets copied which takes more time for more data. For example the strings in an array of strings also get copied, one character at a time. In asm.js data is copied twice, first to a temporary space using a common format both languages can read and write.

Callbacks

Callbacks can be passed to C++ methods by simply adding an argument of type nbind::cbFunction & to their declaration.

They can be called with any number of any supported types without having to declare in any way what they accept. The JavaScript code will receive the parameters as JavaScript variables to do with them as it pleases.

A callback argument arg can be called like arg("foobar", 42); in which case the return value is ignored. If the return value is needed, the callback must be called like arg.call<type>("foobar", 42); where type is the desired C++ type that the return value should be converted to. This is because the C++ compiler cannot otherwise know what the callback might return.

Warning: while callbacks are currently passed by reference, they're freed after the called C++ function returns! That's intended for synchronous functions like Array.map which calls a callback zero or more times and then returns. For asynchronous functions like setTimeout which calls the callback after it has returned, you need to copy the argument to a new nbind::cbFunction and store it somewhere.

Using objects

C++ objects can be passed to and from JavaScript using different parameter and return types in C++ code:

  • by reference using pointers or references (optionally const)
  • by value

Note: currently passing objects by pointer on Node.js requires the class to have a "copy constructor" initializing itself from a pointer. This will probably be fixed later.

Returned pointers and references can be const, in which case calling their non-const methods or passing them as non-const parameters will throw an error. This prevents causing undefined behaviour corresponding to C++ code that wouldn't even compile.

Using pointers and references is particularly:

  • dangerous because the pointer may become invalid without JavaScript noticing it.
  • annoying in asm.js because browsers give no access to the garbage collector, so memory may leak when pointers become garbage without C++ noticing it. Smart pointers are not supported until a workaround for this is implemented.

Passing data by value using value objects solves both issues. They're based on a toJS function on the C++ side and a fromJS function on the JavaScript side. Both receive a callback as an argument, and calling it with any parameters calls the constructor of the equivalent type in the other language.

The callback on the C++ side is of type nbind::cbOutput. Value objects are passed through the C++ stack to and from the exported function. nbind uses C++11 move semantics to avoid creating some additional copies on the way.

The equivalent JavaScript constructor must be registered on the JavaScript side by calling binding.bind('CppClassName', JSClassName) so that nbind knows which types to translate between each other.

Example with a class Coord used as a value object, and a class ObjectExample which uses objects passed by values and references:

5-objects.cc

#include <iostream>

#include "nbind/api.h"

class Coord {

public:

  Coord(signed int x = 0, signed int y = 0) : x(x), y(y) {}
  explicit Coord(const Coord *other) : x(other->x), y(other->y) {}

  void toJS(nbind::cbOutput output) {
    output(x, y);
  }

  signed int getX() { std::cout << "Get X\n"; return(x); }
  signed int getY() { std::cout << "Get Y\n"; return(y); }

  void setX(signed int x) { this->x = x; }
  void setY(signed int y) { this->y = y; }

  signed int x, y;

};

class ObjectExample {

public:

  static void showByValue(Coord coord) {
    std::cout << "C++ value " << coord.x << ", " << coord.y << "\n";
  }

  static void showByRef(Coord *coord) {
    std::cout << "C++ ref " << coord->x << ", " << coord->y << "\n";
  }

  static Coord getValue() {
    return(Coord(12, 34));
  }

  static Coord *getRef() {
    static Coord coord(56, 78);
    return(&coord);
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(Coord) {
  construct<>();
  construct<const Coord *>();
  construct<signed int, signed int>();

  getset(getX, setX);
  getset(getY, setY);
}

NBIND_CLASS(ObjectExample) {
  method(showByValue);
  method(showByRef);
  method(getValue);
  method(getRef);
}

Example used from JavaScript:

5-objects.js

var nbind = require('nbind');

var binding = nbind.init();
var lib = binding.lib;

function Coord(x, y) {
  this.x = x;
  this.y = y;
}

Coord.prototype.fromJS = function(output) {
  output(this.x, this.y);
}

Coord.prototype.show = function() {
  console.log('JS value ' + this.x + ', ' + this.y);
}

binding.bind('Coord', Coord);

var value1 = new Coord(123, 456);
var value2 = lib.ObjectExample.getValue();
var ref = lib.ObjectExample.getRef();

lib.ObjectExample.showByValue(value1);
lib.ObjectExample.showByValue(value2);
value1.show();
value2.show();

lib.ObjectExample.showByRef(ref);
console.log('JS ref ' + ref.x + ', ' + ref.y);

Run the example with node 5-objects.js after installing. It prints:

C++ value 123, 456
C++ value 12, 34
JS value 123, 456
JS value 12, 34
C++ ref 56, 78
Get X
Get Y
JS ref 56, 78

Type conversion

Parameters and return values of function calls between languages are automatically converted between equivalent types:

JavaScriptC++
number(un)signed char, short, int, long
numberfloat, double
number or bignum(un)signed long, long long
booleanbool
stringconst (unsigned) char *
stringstd::string
Arraystd::vector<type>
Arraystd::array<type, size>
Functionnbind::cbFunction
(only as a parameter)
See Callbacks
nbind-wrapped pointerPointer or reference to an
instance of any bound class
See Using objects
Instance of any prototype
(with a fromJS method)
Instance of any bound class
(with a toJS method)
See Using objects
ArrayBuffer(View), Int*Array
or Buffer
nbind::Buffer struct
(data pointer and length)
See Buffers

Type conversion is customizable by passing policies as additional arguments to construct, function or method inside an NBIND_CLASS or NBIND_GLOBAL block. Currently supported policies are:

  • nbind::Nullable() allows passing null as an argument when a C++ class instance is expected. The C++ function will then receive a nullptr.
  • nbind::Strict() enables stricter type checking. Normally anything in JavaScript can be converted to number, string or boolean when expected by a C++ function. This policy requires passing the exact JavaScript type instead.

Type conversion policies are listed after the method or function names, for example:

NBIND_CLASS(Reference) {
    method(reticulateSplines, "reticulate", nbind::Nullable());
    method(printString, nbind::Strict());
}

Buffers

Transferring large chunks of data between languages is fastest using typed arrays or Node.js buffers in JavaScript. Both are accessible from C++ as plain blocks of memory if passed in through the nbind::Buffer data type which has the methods:

  • data() returns an unsigned char * pointing to a block of memory also seen by JavaScript.
  • length() returns the length of the block in bytes.
  • commit() copies data from C++ back to JavaScript (only needed with Emscripten).

This is especially useful for passing canvas.getContext('2d').getImageData(...).data to C++ and drawing to an on-screen bitmap when targeting Emscripten or Electron.

Example:

#include "nbind/api.h"

void range(nbind::Buffer buf) {
  size_t length = buf.length();
  unsigned char *data = buf.data();

  if(!data || !length) return;

  for(size_t pos = 0; pos < length; ++pos) {
    data[pos] = pos;
  }

  buf.commit();
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(range);
}

Example used from JavaScript:

var nbind = require('nbind');
var lib = nbind.init().lib;

var data = new Uint8Array(16);
lib.range(data);

console.log(data.join(' '));

It prints:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

64-bit integers

Normally C++ 64-bit integer types are first converted to double and then to JavaScript number which can only hold 53 bits of precision, but it's possible to preserve all bits by using a bignum class. It should have a constructor taking the following arguments:

  • Integer containing 32 bits from the least important half.
  • Integer containing 32 bits from the most important half.
  • Boolean, true if the number is negative.

It should also have a fromJS function which takes a callback, and calls it with those same arguments to pass the data back to C++ when needed.

An example implementation also capable of printing 64-bit numbers to strings in bases 2, 4, 10 and 16 is included.

Error handling

You can use the NBIND_ERR("message here"); macro to report an error before returning from C++ (#include "nbind/api.h" first). It will be thrown as an error on the JavaScript side (C++ environments like Emscripten may not support throwing exceptions, but the JavaScript side will).

Publishing on npm

Make sure your package.json file has at least the required emcc-path and install scripts:

  "scripts": {
    "emcc-path": "emcc-path",

    "install": "autogypi && node-gyp configure build"
  }

The dependencies section should have at least:

  "dependencies": {
    "autogypi": "^0.2.2",
    "nbind": "^0.2.1",
    "node-gyp": "^3.3.1"
  }

Your package should also include binding.gyp and autogypi.json files.

Shipping an asm.js fallback

nbind-example-universal is a good minimal example of compiling a native Node.js addon if possible, and otherwise using a pre-compiled asm.js version.

It has two temporary build directories build/native and build/asmjs, for compiling both versions. nbind provides a binary copyasm that can then be used to copy the compiled asm.js library into a nicer location for publishing inside the final npm package.

Note that the native version should be compiled in the install script so it runs for all users of the package, and the asm.js version should be compiled in the prepublish script so it gets packaged in npm for usage without the Emscripten compiler. See the example package.json file.

Using in web browsers

nbind-example-universal is a good minimal example also of calling compiled asm.js code from inside web browsers. The simplest way to get nbind working is to add these scripts in your HTML code as seen in the example index.html:

<script src="nbind.js"></script>

<script>
  nbind.init(function(err, binding) {
    var lib = binding.lib;

    // Use the library.
  });
</script>

Make sure to fix the path to nbind.js on the first line if necessary.

Using with TypeScript

nbind has a fully typed API for interacting with C++ code and it can also automatically generate .d.ts files for your C++ classes and functions. This gives you effortless bindings with compile time type checking for calls from JavaScript to Node.js addons and asm.js modules.

All you have to do is compile your C++ code and run the included ndts tool to create the type definitions:

npm run -- node-gyp configure build
npm run -s -- ndts . > lib-types.d.ts

When run in this way, the first argument of ndts is a path from the package root to the binding.gyp file. Typically the file is in the root so the correct path is .

Now you can load the C++ code from TypeScript in three different ways. First import nbind (which also loads the C++ code) and types generated by ndts:

import * as nbind from 'nbind';
import * as LibTypes from './lib-types';

Then choose your favorite way to initialize it:

Purely synchronous:

const lib = nbind.init<typeof LibTypes>().lib;

// Use the library.

Asynchronous-aware:

nbind.init((err: any, binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Promise-based:

import * as bluebird from 'bluebird';

bluebird.promisify(nbind.init)().then((binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Note how there is a type argument <typeof LibTypes> for the init call in all of the examples. It defines types of binding.lib contents, which coming from C++ are otherwise unknown to the TypeScript compiler. You can import the types from a file generated by ndts or just use <any> to disable typing.

For example if you have a C++ class:

struct C : public A, public B {
    A *getA();

    static uint32_t reticulate();
};

And bind it like:

NBIND_CLASS(C) {
    inherit(A);
    inherit(B);

    construct<>();

    method(reticulate);

    getter(getA);
}

ndts will generate the following typings:

export interface _C extends A, B {}
export var _C: { new(): _C };

export class C extends _C {
    /** C(); */
    constructor();

    /** static uint32_t reticulate(); */
    static reticulate(): number;

    /** A * a; -- Read-only */
    a: A;
}

The additional interface _C is generated in this case to support multiple inheritance, because C extends both A and B.

All the tests are written in TypeScript so if you run:

git clone https://github.com/charto/nbind.git
cd nbind
npm install
npm test

You can then open test/test.ts in a TypeScript IDE and see the generated typings in action.

Binding plain C

nbind generates bindings using C++ templates for compile-time introspection of argument and return types of functions and methods.

Since plain C doesn't have templates, there's no standard way to have a C compiler generate new wrapper code for type conversion and output type information available at run-time.

The easiest way to use nbind with C is to write a C++ wrapper calling the C code, and use nbind with that.

Mapping idiomatic C to JavaScript classes may require some manual work, since it's common to reinvent new ways to do object-oriented programming, usually by using structs as classes and simulating methods by passing struct pointers to functions. C++ classes and methods should be used for these.

A good example is libui-node which uses nbind to generate bindings for libui, mainly a C library.

Binding external libraries

If you have external library source code, you should compile it separately into a library first, and then link your Node.js addon with it. If the library has an installation script and the addon is only intended for your own use or other users are willing to do some extra steps, it's easiest to install the library globally first.

For best user experience, libui-node is an example of distributing an external library together with your package.

For creating the actual bindings, see for example this and this message and a tutorial for getting the vg library working.

Debugging

In the browser it can be difficult to stop and debug at the correct spot in optimized C++ code. nbind provides an _nbind_debug() function in api.h that you can call from C++ to invoke the browser's debugger when using asm.js.

For debugging a Node.js addon, if you would normally test it like node test.js, you can instead use gdb node and type run test.js in the GDB prompt. Then in case of a crash, it will show where it happened, inspect the stack etc.

You should also modify nbind.gypi (inside nbind's src directory) and possibly your own binding.gyp, to remove any -O? flags and instead add a -g flag, then remove the build directory and recompile. This allows GDB to show much more information.

Alternatives

Very similar:

Less similar:

Authors

  • Juha Järvi, befunge

Author: charto
Source Code: https://github.com/charto/nbind
License: MIT License
#cpluplus #c 

Comprender La Declaración !important De CSS

!importanten CSS es una notación especial que podemos aplicar a una declaración de CSS para anular otras reglas en conflicto para el selector coincidente.

Cuando trabajamos en proyectos web, es natural que tengamos algunas declaraciones de estilo que anulan otros estilos.

Este no es un problema para un desarrollador experimentado que comprende el mecanismo central de CSS. Sin embargo, puede ser difícil para los principiantes entender por qué el navegador no aplica las declaraciones de estilo que esperan.

Entonces, en lugar de enfocarse en resolver el problema de forma natural, tienden a buscar la solución rápida agregando la !importantdeclaración para hacer cumplir el estilo que esperan. Si bien este enfoque podría funcionar en ese momento, también puede iniciar otro problema complejo.

En esta guía, revisaremos lo siguiente, incluido cómo usarlo !importanty cuándo debemos usarlo:

  • El mecanismo central de CSS
  • Comprender la !importantdeclaración antes de usarla
  • :is()y otras funciones de pseudoclase relacionadas
  • ¿Cuándo exactamente podemos usar la !importantdeclaración?
    • Clases de utilidad
    • Las reglas de estilo que no podemos anular

Suficiente dicho, vamos a sumergirnos.

El mecanismo central de CSS

Comprender los principios básicos de CSS naturalmente nos permitirá saber cuándo es obvio usar la !importantdeclaración. En esta sección, analizaremos algunos de estos mecanismos.

Considere el código HTML y CSS a continuación, ¿de qué color cree que será el texto del encabezado?

Primero, el HTML:

<h2 class="mytitle">This is heading text</h2>

Entonces, el CSS:

h2 {
  color: blue;
}
h2 {
  color: green;
}

¡El texto se volverá verde! Esto es CSS básico fundamental. Con el algoritmo de cascada de CSS , el orden de las reglas de CSS importa. En este caso, gana la última declaración en el código fuente.

Normalmente, esto es lógico. En primer lugar, no debemos repetir el mismo selector que hicimos anteriormente. CSS no quiere repetición, por lo que usa la última regla de declaración.

Sin embargo, hay casos en los que creamos estilos genéricos para los elementos raíz, como el h2, y luego agregamos clases para diseñar elementos específicos. Consideremos también el siguiente ejemplo, comenzando con el HTML:

<h2>This is heading text</h2>
<h2 class="mytitle">This is heading text</h2>

Entonces, veamos el CSS:

.mytitle {
  color: blue;
}
h2 {
  color: green;
}

En el código anterior, el primer h2elemento no tiene clase aplicada, por lo que es obvio que obtiene el color verde del h2selector.

Sin embargo, el segundo h2elemento usa la regla para el selector de clase .mytitle, incluso cuando la regla del selector de elementos viene en último lugar en el código CSS. La razón de esto es que el selector de clase tiene una mayor especificidad en comparación con el selector de elementos.

En otras palabras, el peso aplicado a la declaración en un selector de clase es mayor que el peso del selector de elementos.

De manera similar, la declaración en un selector de ID es más que la del selector de clase. En este caso, el color rojo en el siguiente código tiene prioridad:

<h2 id="maintitle" class="mytitle">This is heading text</h2> 

Seguido por el CSS:

.mytitle {
  color: blue;
}
#maintitle {
  color: red;
}
h2 {
  color: green;
}

Además, un styleatributo en línea tiene prioridad sobre el selector de ID, comenzando con el HTML:

<h2 id="maintitle" style="color: black;" class="mytitle">This is heading text</h2> 

 

Luego seguido por el CSS:

.mytitle {/*...*/}

#maintitle {/*...*/}

h2 {/*...*/}

Este es el flujo de prioridad ideal en CSS y debe mantenerse para evitar anomalías. La !importantdeclaración la mayoría de las veces llega cuando nos olvidamos de estas reglas básicas.

El atributo de estilo en línea y cada uno de los selectores tienen valores que les asignan los navegadores. De esa manera, sabe cuál tiene mayor o menor prioridad. Piense en este valor como un número de cuatro dígitos individuales con el styleatributo asignado al valor de ponderación más fuerte de 1000.

Esto sigue al ID con un valor de 0100, luego a la clase con 0010, y finalmente al selector de elementos con 0001.

A veces podemos combinar selectores dirigidos a elementos específicos, como se ve en el siguiente ejemplo:

<h2 id="maintitle" class="mytitle">This is heading text</h2> 

 

Seguido por el CSS:

h2.mytitle {
  color: blue;
}
#maintitle {
  color: red;
}
h2 {
  color: green;
}

La especificidad del h2.mytitleselector en el CSS anterior es la adición de h2y .mytitle. Es decir, 0001 + 0010 = 0011. Este valor total, sin embargo, es menor que el del #maintitleID que es 0100.

Entonces, el navegador usa la declaración en el selector de ID para anular otras reglas en conflicto. En un caso de igual peso, gana la declaración de la última regla.

Ahora que sabemos qué reglas son las más relevantes y por qué el navegador las aplica, será obvio si usar o no esta !importantdeclaración.

Comprender la !importantdeclaración antes de usarla

Antes de considerar el uso de la !importantnotación, debemos asegurarnos de que seguimos la regla de especificidad y usamos la cascada CSS.

En el siguiente código, tenemos los elementos h2y h3diseñados para ser un redcolor:

<h2 class="mytitle">This is heading II text</h2>
<h3 class="mytitle">This is heading III text</h3>

Luego, .mytitleen CSS:

.mytitle {
  color: red;
}

Pero digamos que en algún momento queremos darle un color al h3elemento . blueAgregar una regla de estilo como la siguiente no cambiaría el color porque la clase tiene más peso y es más específica que el selector de elementos, como hemos aprendido:

.mytitle {...}
h3 {
  color: blue;
}

Sin embargo, usar !importanton the lesserweight hace que el navegador aplique esa declaración sobre otras reglas en conflicto:

.mytitle {...}
h3 {
  color: blue !important;
}

Esto se debe a que la !importantnotación aumenta el peso de la declaración en el orden de precedencia en cascada. Lo que esto significa es que hemos interrumpido el flujo de prioridad normal. Por lo tanto, es una mala práctica y puede generar dificultades en el mantenimiento y la depuración del código.

Si en algún otro punto, queremos anular la regla importante anterior, podemos aplicar otra !importantnotación en una declaración con mayor especificidad (o lo mismo si está más abajo en la fuente). Entonces puede conducir a algo como esto:

h3 {
  color: blue !important;
}

/* several lines of rules */

.mytitle {
  color: green !important;
}

Esto es malo y debe evitarse. En su lugar, debemos comprobar si:

  1. Reorganizar la regla o reescribir los selectores puede resolver el problema en cascada
  2. El aumento de la especificidad del elemento de destino puede resolver el problema

Bueno, averigüémoslo. Volviendo a nuestras reglas de estilo, podemos imponer un bluecolor en el h3elemento aumentando la puntuación de especificidad.

Como se ve a continuación, podemos combinar selectores hasta que su puntaje de especificidad reemplace la regla en conflicto. El h3.mytitleselector otorga una puntuación de especificidad de 0011, que es mayor que la puntuación .mytitlede :0010

.mytitle {...}
h3.mytitle {
  color: blue;
}

Como podemos ver, en lugar de usar la !importantdeclaración para hacer cumplir una regla, nos enfocamos en aumentar el puntaje de especificidad.

:is()y otras funciones de pseudoclase relacionadas

A veces, podemos rastrear problemas hasta una función de pseudoclase. Entonces, saber cómo funciona puede ahorrarnos mucho estrés. Veamos otro ejemplo.

Imagina que estamos trabajando en un proyecto y vemos el siguiente código:

<h1 id="header">
  heading <span>span it</span>
  <a href="#">link it</a>
</h1>
<p class="paragraph">
  paragraph <span>span it</span>
  <a href="">link it</a>
</p>

El uso de las siguientes reglas CSS nos da el resultado después:

:is(#header, p) span,
:is(#header, p) a {
  color: red;
}

Ahora, digamos que queremos darle al spany al texto del enlace en el párrafo otro color de blue. Podemos hacer esto agregando la siguiente regla:

.paragraph span,
.paragraph a {
  color: blue;
}

La regla anterior anulará el bluecolor a pesar de estar más abajo en la línea:

Como una solución rápida, podemos hacer cumplir nuestro bluecolor usando la !importantnotación de esta manera:

:is(#header, p) span,
:is(#header, p) a {...}

.paragraph span,
.paragraph a {
  color: blue !important;
}

Pero, como puede suponer, esa es una mala práctica, por lo que no debemos apresurarnos a usar la !importantnotación. En cambio, podemos comenzar analizando cómo funciona cada selector. La :is()que se usa en el código es una función de pseudoclase para escribir megaselectores en una forma más comprimida.

Entonces, aquí está la siguiente regla en el código anterior:

:is(#header, p) span,
:is(#header, p) a {
  color: red;
}

Lo cual es equivalente a lo siguiente:

#header span,
p span,
#header a,
p a {
  color: red;
}

Entonces, ¿por qué .paragraph spany .paragraph ano anula el color a pesar de tener un puntaje de especificidad de 0011, que es más alto que 0002el de p spany p a.

Bueno, cada selector en :is()usa la especificidad más alta en la lista de argumentos. En ese caso, tanto the #headercomo the pin the :is(#header, p)usan la puntuación de especificidad de #header, que es 0100. Por lo tanto, el navegador mantiene su valor porque tiene una mayor especificidad.

Por lo tanto, cada vez que vemos este tipo de conflicto, es mejor no usar la función de pseudoclase y apegarnos a su equivalente como el siguiente:

#header span,
p span,
#header a,
p a {
  color: red;
}

Ahora, deberíamos poder ver el resultado esperado sin usar la !importantnotación que interrumpe el orden en cascada.

Puedes verlo por ti mismo en CodeSandbox .

¿Cuándo exactamente podemos usar la !importantdeclaración?

A continuación se presentan algunas ocasiones en las !importantque se recomienda usar la notación.

Clases de utilidad

Suponiendo que queremos diseñar todos los botones de una página para que tengan el mismo aspecto, podemos escribir una regla CSS que se puede reutilizar en una página. Echemos un vistazo al siguiente marcado y estilo a continuación:

<p>Subscribe button : <a class="btn" href="#">Subscribe</a></p>

<section class="content">
  <p>
    This <a href="#" class="btn">button</a> style is affected by a higher
    specificity value .
  </p>
  A link here: <a href="#">Dont click</a>
</section>

Seguido por el CSS:

.btn {
  display: inline-block;
  background: #99f2f5;
  padding: 8px 10px;
  border: 1px solid #99f2f5;
  border-radius: 4px;
  color: black;
  font-weight: normal;
  text-decoration: none;
}

.content a {
  color: blue;
  font-weight: bold;
  text-decoration: underline;
}

En el código anterior, podemos ver que el enlace del botón dentro del sectionelemento está dirigido por ambos selectores en el CSS. Y aprendimos que para las reglas en conflicto, el navegador usará la regla más específica. Como esperábamos, .content atiene una puntuación de 0011while .btntiene una puntuación de 0010.

La página se verá así:

En este caso, podemos hacer cumplir la .btnregla agregando la !importantnotación a las declaraciones en conflicto como esta:

.btn {
  /* ... */
  color: black !important;
  font-weight: normal !important;
  text-decoration: none !important;
}

La página ahora se ve como esperamos:

Compruébelo usted mismo en CodeSandbox .

Las reglas de estilo que no podemos anular

Esto sucede principalmente cuando no tenemos control total sobre el código de trabajo. A veces, cuando trabajamos con un sistema de administración de contenido como WordPress, podemos encontrar que un estilo CSS en línea en nuestro tema de WordPress anula nuestro estilo personalizado.

En este caso, la !importantdeclaración es útil para anular el estilo en línea del tema.

Conclusión

La !importantdeclaración nunca está destinada a ser utilizada como deseamos. Solo debemos usarlo si es absolutamente necesario, como una situación en la que tenemos menos control sobre el código o casos muy extremos en nuestro propio código.

Si lo usamos o no depende de cómo entendamos el mecanismo central de CSS, y en este tutorial también cubrimos eso.

Espero que hayas disfrutado leyendo esta publicación. Si tiene preguntas o contribuciones, comparta su opinión en la sección de comentarios y recuerde compartir este tutorial en la web.

Fuente: https://blog.logrocket.com/understanding-css-important-declaration/

#css