GithubHelp home page GithubHelp logo

angular-contentful's Introduction

AngularJS Contentful

AngularJS module to easily access the Contentful content delivery API:

angular-contentful

Build Status

  • lightweight (< 3KB minified)
  • no external dependencies
  • automatically resolves linked content in the response for maximum convenience
  • uses the native AngularJS $http service to connect to the API
  • returns native AngularJS $q promises

Demo

There are working demo's available in the examples directory.

Usage

First install the module using bower:

$ bower install angular-contentful

or npm:

$ npm install angular-contentful

and add the library to your application:

<script type="text/javascript" charset="utf-8" src="bower_components/angular-contentful/dist/angular-contentful.min.js"></script>

The src attribute value above is an example when using bower. Your local library path may vary depending on whether you used bower or npm as your installation method and whether or not you have a build process in place.

Then add the contentful module to the dependencies of your AngularJS application module:

angular.module('yourApp', ['contentful']);

and configure the contentful service in a config block using the provider:

angular
  .module('yourApp')
  .config(function(contentfulProvider){
    contentfulProvider.setOptions({
        space: 'yourSpace',
        accessToken: 'yourAccessToken'
    });
  });

Now you can use one of the directives to fetch Contentful data right from within your markup:

<pre contentful-entry="'6KntaYXaHSyIw8M6eo26OK'">
  {{ $contentfulEntry | json }}
</pre>

or you can use the contentful service anywhere in your application code:

angular
  .module('yourApp')
  .controller('SomeCtrl', function(contentful){
    
    // Get all entries
    contentful
      .entries()
      .then(
      
        // Success handler
        function(response){
          var entries = response.data;
          console.log(entries);
        },
        
        // Error handler
        function(response){
          console.log('Oops, error ' + response.status);
        }
      );
    
  });

The contentful-entry directive

Fetches a Contentful entry asynchronously in the background and makes it available in your child markup as $contentfulEntry as soon as a response from Contentful is received.

Requires a Contentful entry id or a query string to be passed.

Fetch an entry by id

To display an entire entry with id 6KntaYXaHSyIw8M6eo26OK:

<!-- use an Angular expression -->
<pre contentful-entry="entryId">
  {{ $contentfulEntry | json }}
</pre>

<!-- use a literal string, notice the single quotes -->
<pre contentful-entry="'6KntaYXaHSyIw8M6eo26OK'">
  {{ $contentfulEntry | json }}
</pre>

Or to display only one field of the entry:

<h1 contentful-entry="'6KntaYXaHSyIw8M6eo26OK'">
  Hi {{ $contentfulEntry.fields.name }}!
</h1>

$contentfulEntry is available in the child elements as well:

<article contentful-entry="'6KntaYXaHSyIw8M6eo26OK'">
  <section>
    {{ $contentfulEntry.fields.sectionOne }}
  </section>
  <section>
    {{ $contentfulEntry.fields.sectionTwo }}
  </section>
<article>

To make Contentful resolve the linked content in the entry, use include:

<pre contentful-entry="'sys.id=6KntaYXaHSyIw8M6eo26OK&include=3'">
  {{ $contentfulEntry | json }}
</pre>

to specify the number of levels of linked entries to resolve.

Fetch an entry by query string

Often you want to fetch an entry by a property other than sys.id.

Therefore the directive also allows you to specify a query string instead of an id like this:

<h1 contentful-entry="'content_type=dog&fields.slug=bob'">
  Hi {{ $contentfulEntry.fields.name }}!
</h1>

Notice

Behind the scenes all entries matching your query will be fetched and the first item will be assigned to $contentfulEntry.

To reduce data traffic it is highly recommended to use a query string that results in only one entry or add a limit=1 statement to your query like this:

<h1 contentful-entry="'content_type=dog&order=fields.age&limit=1'">
  Hi {{ $contentfulEntry.fields.name }}!
</h1>

The contentful-entries directive

Fetches multiple Contentful entries asynchronously in the background and makes them available in your child markup as $contentfulEntries as soon as a response from Contentful is received.

Takes an optional query string value to pass to the Contentful content delivery API.

For example, to fetch all entries in your space:

<ul contentful-entries>
  <li ng-repeat="entry in $contentfulEntries.items">
    {{ entry.fields.name }}
  </li>
</ul>

Or specify a query string to filter the entries:

<!-- use an Angular expression -->
<ul contentful-entries="querystring">
  <li ng-repeat="dog in $contentfulEntries.items">
    {{ dog.fields.name }}
  </li>
</ul>

<!-- use a literal string, notice the single quotes -->
<ul contentful-entries="'content_type=dog'">
  <li ng-repeat="dog in $contentfulEntries.items">
    {{ dog.fields.name }}
  </li>
</ul>

The optional query string is passed to the Contentful API, so you can use all supported filters.

Links are automatically resolved too, so you can easily access linked content as embedded data like this:

<ul contentful-entries="'content_type=dog'">
  <li ng-repeat="dog in $contentfulEntries.items | orderBy:'fields.name' ">
    <h1>{{ dog.fields.name }}</h2>
    <img ng-src="{{ dog.fields.image.fields.file.url }}" />
  </li>
</ul>

The contentful service

The contentful service can be injected anywhere in your application and exposes the following API:

contentful.asset(id, optionSet)

Get an asset.

Arguments
  • id - {string} - Asset id, required
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.asset(id, 'another');
// or you can pass 'default', although it will implicitly use this key
contentful.asset(id, 'default');
Returns

Promise.

contentful.assets(queryString, optionSet)

Get assets.

Arguments
  • queryString - {string} - Query string to pass to API, optional
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.assets(queryString, 'another');
// or you can pass 'default', although it will implicitly use this key
contentful.assets(queryString, 'default');
Returns

Promise.

contentful.contentType(id, optionSet)

Get a content type.

Arguments
  • id - {string} - Content type id, required
  • optionSet - {string} - optional - Contentful space options (one of keys passed to setOptions method of provider). If not passed it will use default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.contentType(id, 'another');
// or you can pass 'default', although it will implicitly use this key
contentful.contentType(id, 'default');
Returns

Promise.

contentful.contentTypes(queryString, optionSet)

Get content types.

Arguments
  • queryString - {string} - Query string to pass to API, optional
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.contentTypes(queryString, 'another');
// or you can pass 'default', although it will implicitly use this key if parameter is omitted
contentful.contentTypes(queryString, 'default');
Returns

Promise.

contentful.entry(id, optionSet)

Get an entry.

Arguments
  • id - {string} - Entry id, required
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.entry(id, 'another');
// or you can pass 'default', although it will implicitly use this key if parameter is omitted
contentful.entry(id, 'default');
Returns

Promise.

contentful.entries(queryString, optionSet)

Get entries.

Arguments
  • queryString - {string} - Query string to pass to API, optional
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.entries(queryString, 'another');
// or you can pass 'default', although it will implicitly use this key if parameter is omitted
contentful.entries(queryString, 'default');
Returns

Promise.

contentful.space(optionSet)

Get space.

Arguments
  • optionSet - {string} - optional - Contentful space options key passed to setOptions method of provider. Defaults to default key, or if only one space settings are passed it will use that one.
Example
// pass key defined in setOptions method of provider
contentful.space('another');
// or you can pass 'default', although it will implicitly use this key if parameter is omitted
contentful.space('default');
Returns

Promise.

Promises

All methods return a promise.

Depending on the reponse of the API, either the success handler or the error handler is called with a destructured representation of the response with the following properties:

  • data – {object} – The response body transformed with the transform functions.
  • status – {number} – HTTP status code of the response.
  • headers – {function([headerName])} – Header getter function.
  • config – {object} – The configuration object that was used to generate the request.
  • statusText – {string} – HTTP status text of the response.

The data property contains the Contentful data. The other properties are passed for convenience in case you need them.

contentful
  .entries()
  .then(
  
    // Success handler
    function(response){
      var entries = response.data;
      console.log(entries);
    },
    
    // Error handler
    function(response){
      console.log('Oops, error ' + response.status);
    }
  );

Automatically resolved linked content

Angular-contentful automatically resolves linked content for you.

If the Contentful API response includes linked content such as linked entries or linked assets, they are automatically attached to their parent content for maximum convenience.

Suppose you have a collection of dogs that have an image linked to them, you can now access the image as a direct property instead of having to resolve the image manually:

<ul contentful-entries="'content_type=dog'">
  <li ng-repeat="dog in $contentfulEntries.items | orderBy:'fields.name' ">
    <h1>{{ dog.fields.name }}</h2>
    <img ng-src="{{ dog.fields.image.fields.file.url }}" />
  </li>
</ul>

Due to how the Contentful API works, linked content is only available when using contentful-entries, not when using contentful-entry. Read more details here.

Notice

Resolving links hierarchically can cause circular links.

Although this isn't harmful, it may hamper you from outputting the entire response e.g. using {{ $contentfulEntries | json }}.

Connecting to multiple spaces

If you need to connect to more than one Contentful space, you can specify additional spaces in the contentfulProvider configuration:

angular
  .module('yourApp')
  .config(function(contentfulProvider){
    contentfulProvider.setOptions({
        'default': {
          host: 'cdn.contentful.com',
          space: 'first_space',
          accessToken: 'first_token'
        },
        'another': {
          host: 'cdn.contentful.com',
          space: 'second_space',
          accessToken: 'second_token'
        }
        ...
    });
  });

and pass in the space key as the optionSet argument when calling a contentful service method:

angular
  .module('yourApp')
  .controller('SomeCtrl', function(contentful){
    
    // Get all entries
    contentful
      .entries('', 'another')
      .then(
      
        // Success handler
        function(response){
          var entries = response.data;
          console.log(entries);
        },
        
        // Error handler
        function(response){
          console.log('Oops, error ' + response.status);
        }
      );
    
  });

If you initialize contentfulProvider with only one set of options, it will be treated as the default one.

Currently, the directives do not allow you to specify a space and will always connect to the default space.

Example raw Contentful responses

These raw response examples give you an idea of what original Contentful responses look like.

If you are interested in the details, please visit the Contentful delivery API documentation.

Example Contentful response for successful request

{
  "data": {
    "sys": {
      "type": "Space",
      "id": "cfexampleapi"
    },
    "name": "Contentful Example API",
    "locales": [
      {
        "code": "en-US",
        "default": true,
        "name": "English"
      },
      {
        "code": "tlh",
        "default": false,
        "name": "Klingon"
      }
    ]
  },
  "status": 200,
  "config": {
    "method": "GET",
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "params": {
      "access_token": "b4c0n73n7fu1"
    },
    "url": "https://cdn.contentful.com:443/spaces/cfexampleapi"
  },
  "statusText": "OK"
}

Example response for error

{
  "data": {
    "sys": {
      "type": "Error",
      "id": "NotFound"
    },
    "message": "The resource could not be found.",
    "details": {
      "sys": {
        "type": "Space"
      }
    },
    "requestId": "71a-1131131513"
  },
  "status": 404,
  "config": {
    "method": "GET",
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "headers": {
      "Accept": "application/json, text/plain, */*"
    },
    "params": {
      "access_token": "b4c0n73n7fu1"
    },
    "url": "https://cdn.contentful.com:443/spaces/cfexampleapiii"
  },
  "statusText": "Not Found"
}

Why not use the official contentful.js?

The official Contentful to way is to include 2 libraries in your application:

<script type="text/javascript" charset="utf-8" src="bower_components/contentful/dist/contentful.min.js"></script>
<script type="text/javascript" charset="utf-8" src="bower_components/ng-contentful/ng-contentful.js"></script>

The problem

contentful.js is the main Contentful JavaScript library that relies on different external libraries such as:

  • questor to perform HTTP requests
  • bluebird for promises

and then bundles everything together in contentful.js using Browserify, resulting in a file that packs over 100KB minified.

ng-contenful.js then forms a wrapper around contentful.js that takes care of converting the bluebird promises back to AngularJS promises.

This makes sense if you are in a non-AngularJS environment such as node.js, but AngularJS already has built-in services to perform HTTP requests and provide promises.

The solution

This AngularJS module uses native AngularJS services to provide a similar API using:

  • $http to perform HTTP requests
  • $q for promises

which results in:

  • NOT having to include contentful.js and ng-contentful.js, saving you an expensive 100KB+ client side download when your application loads
  • less CPU cycles in the client by not having to convert promises

Contribute

To update the build in the dist directory:

$ gulp

To run the unit tests using the src files:

$ gulp test-src

To run the unit tests using the unminified library:

$ gulp test-dist-concatenated

To run the unit tests using the minified library:

$ gulp test-dist-minified

Change log

v2.2.0

v2.1.0

  • Added module property to package.json for webpack compatibility

v2.0.0

  • BREAKING CHANGE: Added support for specifying expressions in directive attributes

v1.1.0

  • Added query string support to contentful-entry directive

v1.0.0

  • Simplified service API so it always resolves links by default
  • Simplified contentful-entries directive API to make data available more intuitively using $contentfulEntries instead of $contentfulEntries.entries
  • Simplified contentful-entry directive API to make data available more intuitively using $contentfulEntry instead of $contentfulEntry.entry
  • Removed support for success and error shorthand methods in favor of more consistent API with then.
  • Updated documentation

v0.5.1

  • Update contentful-entries directive so it return response identical to contentful service method

v0.5.0

  • Added support to automatically resolve links when multiple entries are returned
  • Updated documentation

v0.4.0

  • Added contentfulEntries directive
  • Added additional unit tests
  • Updated documentation

v0.3.0

  • Added contentfulEntry directive
  • Added additional unit tests
  • Updated documentation

v0.2.0

  • Added demo application
  • Added shorthand support for success and error handlers
  • Added documentation

v0.1.0

  • Added contentful service
  • Added unit tests
  • Added initial documentation

angular-contentful's People

Contributors

jvandemo avatar oldskool73 avatar raarellano avatar traj avatar vuk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-contentful's Issues

Connecting to multiple spaces

Is there a way to instantiate angular-contentful for using two spaces with two access tokens in same application (possibly on same "page")

filter don't work

Hi!
I can't apply filters as object like contentful.entry({"content_type":"foo"}).then()...
is that a problem with your library or with contentful?

pull in content from more than one space

Hello, this isn't an issue with the repo, more like an issue with me, but how would I pull in content from more than one contentful space?

Great repo, btw; it was quite easy to get this up and working.

Strict Contextual Escaping

This isn't an issue, but rather a question. Is there a way to disable sce for certain fields? I need to display my fields with the html markup provided by a contentful text field.

Query entries get response Bad Request

I tested the contentful entries query:

Failed to load resource: the server responded with a status of 400 (Bad Request)
https://cdn.contentful.com/spaces/1jdsnorf6e9t/entries?access_token=02d0e52b0cfb62cb9fc011cfe849191fd58a9abb285a388afd68720cf9ab1df7&content_type=category

I switched from dog to category because I don't have dogs in my blog, other then that, I used it this way:

ul contentful-entries="content_type=dog"
li ng-repeat="dog in $contentfulEntries.items"
{{ dog.fields.name }}
/li
/ul

I have also tried with CURL and followed the documentation: https://www.contentful.com/developers/documentation/content-delivery-api/http/#search-content-type.

curl -X GET -H 'Authorization: Bearer 02d0e52b0cfb62cb9fc011cfe849191fd58a9abb285a388afd68720cf9ab1df7' 'https://cdn.contentful.com/spaces/1jdsnorf6e9t/entries?content_type=category';
{
"sys": {
"type": "Error",
"id": "InvalidQuery"
},
"message": "The query you sent was invalid. Probably a filter or ordering specification is not applicable to the type of a field.",
"details": {
"errors": [
{
"name": "unknownContentType",
"value": "DOESNOTEXIST"
}
]
},
"requestId": "c0e-1773132314"
}

Maybe only dogs is searchable...

modifying api responses

Hi,

I'm quite new to contentful and the angular-contentful-module but it looks very good and I love your project. It helped me a lot. I just got notified about new options to modify the api responses in this article: https://www.contentful.com/r/knowledgebase/modifying-api-responses/

It looks like that option would help me a lot to downsize the response.data to only the fields I need. Is this modyifing also posisble in the angular-contentful-module?

Regards, Peter

How can I sort the contentful response on the frontend?

I love contentful but it would be half as great without your work. Thanks!

How can I sort the contentful response on the frontend? Below is my attempt.

https://jsfiddle.net/k3jmq2gm/1/

<div class="columns teams-list" contentful-entries="'content_type=staff'" ng-controller="teamContentful">
	<div ng-repeat="item in $contentfulEntries.items | orderBy:'item.fields.name'" class="column" data-flag="{{item.fields.flag}}" data-visible="{{item.fields.visible}}" ng-class='{active:$first}' data-tab="1" data-title="{{item.fields.title}}" data-name="{{item.fields.name}}">
		<!-- <h1>{{item[0].title}}</h1> -->
		<img src="{{ item.fields.image.fields.file.url }}" title="{{item.img}}">
		<p class="hide bio">{{item.fields.bio}}</p>
	</div>
</div>

Using angular-contentful in Angular 2.0

Hi,

Just wondering if you've seen any success of people using this module in an angular 2.0 application. I am starting a new Ionic project and i've used this before in previous Angular 1.0 applications and loved it.

Just wondering how you would go about using this in an Angular 2.0 project if it is possible.

Multiple spaces: requests are missing the host

I may be doing something incorrectly here, so I apologize for my ignorance if that's the case.

I have a contentful.config.js file that has the following so that I can reference multiple spaces:

function ContentfulConfig(contentfulProvider) {
    contentfulProvider.setOptions({
        'default': {
            space: `${space_id}`,
            accessToken: `${access_token}`
        },
        'another': {
            space: `${space_id}`,
            accessToken: `${access_token}`
        }
    });
}
ContentfulConfig.$inject = ['contentfulProvider'];
export default ContentfulConfig;

In my app.routes.js I have the following (using ui-router):

function appRoutes($stateProvider) {
    $stateProvider
        ...
        .state('app.cli', {
            url: '/cli',
            template: cliTemplate,
            controller: 'CLIController as cli',
            resolve: {
                contents: ['contentful', (contentful) => {
                    return contentful
                        .entries('', 'jscli')
                        .then((response) => {
                            return response.data;
                        }, (error) => {
                            console.log(`Error: ${response.status}`);
                        });
                }]
            }
        });
}
appRoutes.$inject = ['$stateProvider'];
export default appRoutes;

When navigating to this page, so that a call is made to the API for the requested data, I was getting a malformed URI error.

Delving in I found that the request was going to http://:80/spaces/${space_id}/entries.

It was missing the host.

Ensuring that the host is supplied among the options in contentful.config.js for each space resolved the issue.

I'm assuming that this might be as easy as adding this instruction to the documentation examples, but I didn't dig deep enough to learn why the host wasn't remaining intact when merged on line 313 of angular-contentful.js in this.setOptions() method.

Edit: grammar.

Brackets gets converted to entities

When I try to use a variable with double curly brackets they get converted to entities in the resulting get request.

<div contentful-entry="{{ eventId }}">{{ $contentfulEntry | json }}</div>

becomes

https://cdn.contentful.com/spaces/osmot86btiv3/entries/%7B%7BeventId%7D%7D

Is there a way to solve this?

Allow access to $contentfulEntries.includes

I don't know if this is a new change on contentful's part or not, but I see in the response packet from, say, a call like this https://cdn.contentful.com/spaces/XXX/entries?access_token=XXX&content_type=XXX now returns an includes property alongside items, and it would be nice to be able to access that directly, in addition to continuing to auto-populate the linked Resources

Sample response:

{
  "sys": {
    "type": "Array"
  },
  "total": 1,
  "skip": 0,
  "limit": 100,
  "items": [
    {
      "sys": {…},
      "fields": {
        "title": "…",
        "downloads": [
          {
            "sys": {
              "type": "Link",
              "linkType": "Asset",
              "id": "…"
            }
          }
        ],
      }
    },
    …
  ],
  "includes": {
    "Asset": [
      {
        "sys": {…},
        "fields": {
          "file": {…},
          "title": "…"
        }
      },
      …
    ]
  }
}

Incorrect 'orderBy' syntax

The docs show you should use | orderBy:'dog.fields.name as show in your example:

<ul contentful-entries="'content_type=dog'">
  <li ng-repeat="dog in $contentfulEntries.items | orderBy:'dog.fields.name' ">
    <h1>{{ dog.fields.name }}</h2>
    <img ng-src="{{ dog.fields.image.fields.file.url }}" />
  </li>
</ul>

However; This didn't work for me and cascading through the objects, I found that I needed to drop the category name in the orderBy filter and only apply it as | orderBy:'fields.name' as shown below:

<ul contentful-entries="'content_type=dog'">
  <li ng-repeat="dog in $contentfulEntries.items | orderBy:'fields.name' ">
    <h1>{{ dog.fields.name }}</h2>
    <img ng-src="{{ dog.fields.image.fields.file.url }}" />
  </li>
</ul>

Just a heads up that you may want to update the docs, or if they are correct and I'm using them incorrectly I'd appreciate the feedback.

Example only shows garbage

I have implemented the all content example:

{{ $contentfulEntries.total }} items found:

  • {{ entry.sys.id }}

There is only a list of garbage. How do I get some actual content from this?

contentful.contentType(id) bypassing arguments

Hey!

I was trying to use the contentful.contentType(id) passing the contentType id via controller but it ends up fetching the whole API instead of the specific one. To solve that problem I had to use the directives method although I really want to have that in the backstage instead of in the HTML.

Here´s the example:

angular
     .module('app')
     .controller('HomeEvents', function ($scope, contentful) {

       var contentType = '43ynb0LYp2eckqQss68os4';

       // Get all entries
       contentful
         .entries({ content_type: contentType })
         .then(

           // Success handler
           function(response){
             var entries = response.data;

             $scope.events = entries.items;

           },

           // Error handler
           function(response){
             console.log('Oops, error ' + response.status);
           }
       );

     }); 

Could you take a look at that please?

GREAT lib! Thank you so much!

Retrieving an image as a directive

@jvandemo - is it possible to retrieve an image from contentful as a directive or do i have to use the service for that? Thanks!

<img contentful-entry="'6KntaYXaHSyIw8M6eo26OK'" src="$contentfulEntry.fields.url"/>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.