We need to figure out a better way to categorize B2B startups. SaaS, PaaS or IaaS aren’t descriptive enough for API companies, this is the path to Embedly using Cloud API.

Humans are incredibly good at categorization. We love putting labels on things and if we can’t find a label, we use one of our existing schemas to draw a comparison. This is why most new startups are described as X for Y (AirBnB for Food, Uber for Cats).

SaaS, PaaS and IaaS were all created with this comparison in mind. I mean, Software As A Service just screams “we didn’t know what to call a hosted version of Microsoft Products so we used an X for Y”.

Each have very specific meanings in this world.

SaaS: “A software delivery model in which software and associated data are centrally hosted on the cloud.” Examples include Salesforce, Google Apps and Microsoft Office 365. An individual user interacts with the product.

IaaS: “In the most basic cloud-service model, providers of IaaS offer computers - physical or (more often) virtual machines - and other resources.” Examples include AWS, Rackspace and Dyn.

PaaS: “In the PaaS model, cloud providers deliver a computing platform typically including operating system, programming language execution environment, database, and web server.” Examples include Heroku, Google App Engine and dotCloud.

These terms are incredibly important. They allow a customers/developers/investors to frame a conversation around a new product by putting it into one of these predefined categories. They allow Forrester to create outrageous claims about the market and get sales and marketing people excited.

This is all well and good until you don’t fit into any of these models. We don’t sell to the end user, we sell to the dudette that builds the SaaS app.  We don’t allow you to host your own VM and we don’t give you a layer on top of the VM like Heroku does.

People have started getting creative with these terms. Mobile backend companies decided that they didn’t like any of these labels, so they created their own.

BaaS: Backend as a Service. “A model for providing web and mobile app developers with a way to link their applications to backend cloud storage while also providing features such as user management, push notifications, and integration with social networking services.” Examples include Parse, Kinvey and StackMob.

This term can now be used it successfully to market and sell product.

Let’s talk about a segment of the market that explicitly sells to developers, but does not host code. How do they frame themselves?

  • Twilio is put into IaaS, but describes themselves as a “Cloud API”.
  • Zencoder describes themselves as a SaaS company.
  • SendGrid is a “Cloud Based Service”.
  • Filepicker says they are SaaS.
  • Mashcape is the “Cloud API Hub”.
  • Imagga is a “Cloud Platform”.

Here’s the deal guys, we need to pick. I believe, like every web business, we probably fall into SaaS, but that’s not specific enough. Selling an API to a developer is completely different then selling Yammer to a user/organization.

The boundary for this definition is the following.

“An organization who’s main product is a RESTful API that is sold to developers on a metered basis.”

A few options:

  • AaaS: API as a Service
  • FaaS: Feature as a Service
  • DaaS: Data as a Service

Those are all really bad and we should probably stay away from an “As a Service”, because service will always be redundant.

From a marketing and understanding purpose, my vote is Cloud API. This is not a new term, Wikipedia describes it as:

Cloud APIs are application programming interfaces (APIs) used to build applications in the cloud computing market. Cloud APIs allow software to request data and computations from one or more services through a direct or indirect interface.

Cloud, while overused, allows even non technical customers to get an understanding of what we do. We are in “The Cloud”! It at least begs the question, “what is an API?”

While we could use REST or Web, neither conveys that message as broadly as “Cloud”.

API, while technical, clearly defines the boundary. Service, Platform or Provider are all too broad and won’t differentiate ourselves from the next company.

From here on out Embedly will be known as a Cloud API company. If you are in the space, we hope you join us.

Posted 13 hours ago by screeley

Pinterest has built a rather clever image loading technique that utilizes the dominant color of an image as a placeholder while the browser readies the image. The following jif gives you an idea of how it works:

Pinterest Loading Gif

“Hey hunny, will you come look at this” has become the universal call for “let me show you something pretty on Pinterest” (at least in my household). While the pin is always pretty I’ve been constantly impressed with the little design tweaks that Pinterest adds between each beckoning.

I’ve been told that if you really want to impress a girl, you recreate a feature of the site she loves. I’m basically holding up a boom box on my girlfriend’s front lawn by writing this. 

Pinterest has figured out that the default loading behavior for images is really ugly. The browser fills the image from top to bottom, stops at random places depending on the connection and generally looks like hell.

It would be nicer if the image just faded in when it was ready for your viewing pleasure. To boot, let’s use the dominant color of the image as a placeholder to make the transition even less jarring.

It’s _very_ pretty, so we decided to build it using Embedly’s tech. Here’s a jif of our version:

Pinterest Loading Gif

We built a tutorial on the site so you can view the demo here: embed.ly/docs/tutorials/background

A 3 sentence primer on how we built it:

We used Embedly’s Extract API to get the dominate color of the first image and used it to fill a placeholder div. Embedly’s Display API is then used to resize the image to fit in in the container. We listen for the “onload“ event on the image and then animate the opacity to 1. 

Needless to say in about 30 lines of JavaScript we can reproduce a feature that can make any site look better. I leave you with the immortal words of Peter Gabriel: 

“in your eyes oh, I want to be that complete”

Posted 2 days ago by screeley

It’s easy to overlook among all of Tumblr’s social features and stunning design, but the cobblestones that paved the road to Tumblr’s success are embeds. They enable the curation and communities that make Tumblr an engaging and compelling platform.

The thoughtful integration of embeds allows users to easily post and display pictures, videos, and text over a range of themes and devices.  From this, media all around the web becomes an object of self-expression, creating one of the richest, most meaningful media ecosystems.  

Amidst Tumblr’s recent acquisition, I want to highlight the relationship of embeds, reblogging, and building a meaningful product. 

Embeds are a crucial part of the Tumblr experience

Embeds are so important that they are woven into the content creation and consumption of Tumblr. As soon as you log in, you see:

image

Why wouldn’t text posts be enough? You could easily include photos and videos as a text post.  Tumblr wants to make sure the Dashboard and custom themes embed the media properly, and includes options to post specific media so it displays them efficiently over a range of devices and themes.  This is crucial especially on mobile: images are resized and videos are given slideshow previews.

Rich embeds and the rise of reblogging

Reblogging follows naturally from Twitter’s retweets. Both improve findability of interesting users by spreading content.  

The crucial difference is that the reblog embeds the images, gifs, or movies, and the retweet does not.   Because of the rich media, the reblog is more expressive than the retweet.  It is a way to easily express part of your own identity, through an image or a gif. 

Reblogging makes it easier to become a curator, reblogging specific subjects, or based on taste.  You don’t have to produce great content to be popular on Tumblr, you can do it just based on your taste and what you reblog. 

Communities around the curator

One effect of having more curators is that reblog depth is larger than retweet depth.  Retweet depth on average is less than three, whereas reblog depth can be larger, happening in cascades.  Even if it’s a few months old, once a popular ‘curator’ reblogs your post, you’ll see many more likes, reblogs, and follows.

The rise of curators has produced a media ecosystem where you can find communities around everything related to taste. You can find music tumblogs for the most niche genres of music- the same for fashion, for food, and for visual art.  From such a rich set of media, you will certainly find things that you find beautiful, and you’ll want to come back for more.

The bottom line

Tumblr built embedding right into its product, which made reblogging more expressive, thus producing one of the richest and most meaningful media platforms. 

Posted 3 days ago by whichlight

Build vs Buy is a classic debate. Do we spend the money on internal resources to build a product or do we just pay someone else to do it for us?

The common approach is to just compare what it will cost internally vs externally. Two Engineers for 6 months vs X dollars for the product.

Rather than thinking about the product itself it’s helpful to think of the intangibles that aren’t inherent in the buy, the things that would be difficult to obtain by building the product yourself.

So what are you really getting when buying?

1. You are buying expertise.

While your two engineers are probably excellent, they haven’t lived and breathed a space for years.

The SendGrid team are experts at email delivery. While most developers can set up their own Email server they can’t tell you exactly why an Email isn’t getting delivered and how you can fix it.

The Zencoder team are experts at quickly converting any video into any format. They know where the browser and mobile market is going and can get you there faster.

The Crocodoc team are experts at converting the many horrors of Microsoft products into HTML5. Every edge case can be properly rendered and look exactly the same on any platform.

2. You are buying time.

Anything that is outside of your core competency should be delegated to someone else. Unless you plan on competing with the company, you should probably just buy their product.

3. You are buying QA.

“Given enough eyeballs, all bugs are shallow”. When one developer finds a bug with an API, the community benefits. It’s almost impossible to catch all edge cases even with great testing practices. With a large community of developers and their users your QA department is almost infinite.

4. You are buying community.

There are a massive amount of talented engineers out there that are already building interesting products using any given API. A quick search of StackOverflow shows that there are 332 questions tagged with Twilio, 208 with SendGrid and 164 with filepicker.io to name a few.

You are in good company when you buy.

5. You are buying scale.

Interesting things start to happen at scale. Embedly’s average response time has dropped dramatically over the past year. As the service has grown and more developers use the product there is a greater probability that a URL requested is already in cache. This allows use to respond quicker and I assume this is also the case with a company like FullContact.

6. You are buying relationships.

There is almost always someone upstream that can cause issues. Twilio has the Phone Companies, SendGrid has Email providers, FullContact has Social APIs, Filepicker has Dropbox and Embedly has Providers. At scale you have probably already cultivated relationships that when something fails you know exactly who to talk to.

Embedly buys MixPanel, SendGrid, FullContact, Zendesk, GitHub and Chargify.

Who are you buying time from?

Posted 1 week ago by screeley
Tagged:

By using FullContact to push user profile data into Mixpanel we’re able to make targeting and tracking more powerful. We can target drip marketing campaigns more precisely based on where our users work, as well as their social profiles, location or age.

For those that are unfamiliar, Mixpanel helps you track your users and customers as they use your application, hopefully providing insight into how to increase conversion rates (“revenue analytics”). FullContact is a cool little API that takes an email address and provides lots of information about the person behind that address — his or her social network profiles, places of employment and some basic demographic information.

We are trying to get to a place where we can trigger notifications based on personal attributes instead of just when they signed up. Here is an example of targeting made possible by FullContact and Mixpanel:

image

The following is a short tutorial on using the FullContact API to push user data into Mixpanel. 

First off, this is what a user might look like in your Mixpanel project:

{
    "email": "sean@embed.ly",
    "plan": "basic",
    "date_joined": "May 1, 2012",
    "last_login": "May 10, 2013"
}

This information is probably populated by your frontend via the Mixpanel javascript library, but it’s perfectly reasonable to do it in the backend as well. Fullcontact, however, is a separate API that you’ll have to call on your backend. Looking up an email address is very straightforward via its HTTP API:

$ curl "http://api.fullcontact.com/v2/person.json?email=sean@embed.ly&apiKey=YOURKEY"

{
  "status": 200,
  "likelihood": 0.89,
  "requestId": "aa2f9f2c-57b8-48ab-811b-1be24aa652c8",
  "photos": 
  ...
  "demographics": {
    "locationGeneral": "Boston",
    "gender": "Male"
  },
  ...
}

Take a look at the full response if you’d like to see what kind of information it can provide. Pretty simple, although there are a couple details to keep in mind:

  • Ocassionally Fullcontact will return HTTP 202 which indicates your search has been queued and that you should retry the request in a few minutes
  • A status code of 404 means the query has run and no information was found
  • Fullcontact will rate limit your requests based on your plan

This means you’ll want to do your lookups in batches, which might look something like this:

def fullcontact_lookup(emails, api_key):
    results = {}
    to_lookup = set(emails)
    api_format = 'http://api.fullcontact.com/v2/person.json?email=%s&apiKey=%s'
    while len(to_lookup) > 0:
        # make a copy so we can delete from the set while we iterate
        for email in to_lookup.copy():
            api = api_format % (email, api_key)
            response = json.loads(requests.get(api).text)
            if response['status'] == 200:
                results[email] = response
                to_lookup.remove(email)
            elif response['status'] == 404:
                results[email] = None
                to_lookup.remove(email)
            elif response['status'] == 202:
                # keep it around to give it another go
                pass
            else:
                raise ValueError('Unexpected response from fullcontact: %s' %
                    json.dumps(response))
        # give fullcontact a minute to run some queries
        time.sleep(60)
    return results

Now we need to actually stuff this data into Mixpanel. Unfortunately, naively running fullcontact_lookup against your entire database every night will be expensive, as Fullcontact charges per successful request. What we can do is add a property to each person called, say, fullcontact_version that keeps track of who has been queried. This can be an integer that gets incremented each time you change your schema, so you can just run the people with fullcontact_version != current_version. Treat the code below as pseudo-code; if you’d like to see the nitty gritty Mixpanel API details check out their docs.

def go(fullcontact_version):
    to_update = get_with_different_version(fullcontact_version)
    all_info = fullcontact_lookup(to_update)
    for person in to_update:
        info = all_info[person['email']]
        person['fullcontact_version'] = fullcontact_version
        if info:
            person['age'] = info.get('demographics', {}).get('age')
            # add more properties as you see fit
        save_person_to_mixpanel(person)

And there it is! With this small amount of effort you can get greater insight into who your users are and the ability to communicate with them more efficiently.

Posted 1 week ago by thejohnnest

Our newest product is an Image Proxy and Image Resize API, fondly referred to as Display. This service is being thrown into the lime-light today to highlight its simplicity and usefulness for providing optimized 3rd party images to your web or mobile application.

Display is useful in many situations, but we find that some of the most important features include its use as a proxy to provide SSL images and its ability to resize images, which not only decreases the file size, but also allows you to scale them to fit and load quickly in any mobile or web browser.

The Image Resize API is a great complement to any 3rd party media API aggregating photos and images of various sizes. It integrates directly with our own Embed and Extract APIs to provide SSL images and resizing automatically.

image

See the Display image resize feature in action in the Resize demo or try some examples with Embedly JQuery.

Images make your site more engaging, but they slow down page load time, and it’s hard to optimize them for different screens.  Display solves that problem. Sign up to get started with Display.

Posted 1 week ago by artgibby

Vine is doing well outside of Twitter, better than it’s competitors, but it’s not a rocket ship.

We set out to measure the rise of Vine as a provider and see how it compares to other mobile video sharing tools. Here are the total URLs that we have seen posted to Embedly’s API since Vine launched on January 24, 2013:

image

You’ll see a nice bump post launch and the classic exponential growth that you would like to see, but that wore off after about a week. Vine’s competitors are barely a blip on the radar, so let’s look at this graph on a logarithmic scale:

image

Vine still dominates, SocialCam seems to do a bit better than Qik and Viddy who are about equal. 

How many URLs we get through the system is not that important because that can change on any day due to one client hammering us with all Vine URLs. It’s more about many different developers are requesting a certain provider along with velocity.

The following chart scores providers based on the number of URLs and the diversity of developers. It is a little more telling of how well Vine is doing:

image

Vine is trending up, while the rest have a slight trend downwards.

Vine is getting some good distribution throughout the long tail and it’s pretty clear why Viddy is giving back 18 million dollars.

Finally, to put this in perspective we did 1,345,449 YouTube URLS, 243,897 Instagram URLs and 25,892 Vine URLs yesterday. Vine still has a long way to go.

Posted 2 weeks ago by screeley

In this fourth post in our series on Ember, we will continue to work from the development environment that we worked on in the first three posts.

This installment will focus on models, backed by RESTful storage, using ember-data. First, a giant, red, warning: ember-data is not production ready. It is under active development and changes often. There are bugs and limitations and you’ll have to wait for them to be fixed. We at Embedly, in our youthful exuberance, decided to ignore these problems and blaze forward into the future! If you do decide to follow us, stay up-to-date with breaking changes at BREAKING_CHANGES.md.

As usual, you can find the complete code for the demo project at github. We won’t cover every code change here, just the ones directly related to ember models and ember-data.

If you remember, in our last installment, we created a team page. We used fixtures to provide data to the template. Let’s switch out these fixtures for some data provided by a RESTful api. We’ve built a simple api server in node.js to connect to. It is only an example and does the bare minimum to get us up and running. Please don’t consider using it for anything remotely serious.

Adding ember-data

We are using bower to manage our javascript dependencies. There is an ember-data wrapper, called ember-data-shim, made specifically for dependency management. If you use it, make sure it is relatively up-to-date when you add it to your own project. Here is our new component.json.

    // bower.json

    {
      "name": "demo",
      "version": "0.0.0",
      "dependencies": {
        "modernizr": "~2.6.2",
        "jquery": "~1.9.1",
        "handlebars": "1.0.0-rc.3",
        "ember": "1.0.0-rc.3",
        "ember-data-shim": "0.0.12"
      },
      "devDependencies": {}
    }

Running bower install will install the dependency into our app/components path. Now we just have to add ember-data.js to our app/index.html.

    // app/index.html

    - snip -
    <script src="components/ember/ember.js"></script>
    <script src="components/ember-data-shim/ember-data.js"></script>
    - snip -

If everything went as planned, we should be able to start the app, open it in the browser, and check to see if there is a global DS object available in the browser’s javascript console.

Configuring ember-data

There is a little bit of configuration to do to get ember-data working. We’ll add it to app/scripts/main.js. The first part tells ember-data what version we are planning on using. This is important since the API has breaking changes between revisions. We don’t want to accidentally upgrade before we are ready. The second section tells the RESTAdapter the base URL for our RESTful api.

    // app/scripts/main.js

    - snip -
    App.Store = DS.Store.extend({
      revision: 12
    });

    DS.RESTAdapter.reopen({
      url: '/rest/1'
    });

    - snip -

You may be asking yourself, what is the RESTAdapter, and what other adapters are there? Ember-data comes packaged with a RESTAdapter and FixtureAdapter. The FixtureAdapter provides an in memory browser store and is mostly useful for testing and development.

FixtureAdapter was an option when writing this post, but I also wanted to show the expected responses and results of the API calls, so that when you go to implement your own RESTful service, you’ll know what to expect.

Defining Our Model

Defining our model is very simple. First we’ll remove all our “Fixtures” from app/scripts/modules/about.js, and replace them with our model definitions in app/scripts/modules/models.js.

    // app/scripts/modules/models.js

    App.Org = DS.Model.extend({
      name: DS.attr('string'),
      description: DS.attr('string'),
      members: DS.hasMany('App.Member')
    });

    App.Member = DS.Model.extend({
      name: DS.attr('string'),
      org: DS.belongsTo('App.Org')
    });

    // app/index.html

    - snip -
    <script src="scripts/modules/models.js"></script>
    - snip -

Here we are only using the string attr type, but string, number, boolean, and date are also available. It is also possible to define your own attr types as well, but we won’t cover that here.

We are also defining a many-to-one relationship between Member and Org. We really only have one Org, but who knows how our site will grow in the future. It is possible to declare many-to-many relationships, but let’s keep things simple.

Wiring up our Route and Controller

Let’s update the about page to use our new model, replacing our old fixture code.

    // app/scripts/main.js

    App = Em.Application.create({
      rootElement: $('#app'),
      orgId: 0
    });
    - snip -

    // app/scripts/modules/about.js

    - snip -
    App.AboutRoute = Em.Route.extend({
      model: function() {
        return App.Org.find(App.get('orgId'));
      }
    });

    // app/templates/about.handlebars

    - snip -
    {{render "team" controller.members}}
    

We’ve configured our orgId in the App object. This is a good place to put site-wide configuration. Next we replaced our fixture with a call to App.Org.find() in our Route#model. ember-data will return an empty object, then switch in the Org when it’s ready. Since Ember.js is so wonderful, it will re-render the relevant parts of the view on update. Finally, we replace the members fixture in the about.handlebars with the members field of the Org object, which is an Array of organization members.

Now our application behaves exactly the same as it did before we implemented ember-data, except that it is backed by a RESTful API. Pretty simple, eh? But not very interesting, we’ll have to add some CRUD operations to truly demonstrate ember-data’s power. Let’s take a look at, what’s happening between the server and client before we move on to mutating the data.

Retrieving records using ember-data is extremely simple. One thing to keep in mind is that it’s asynchronous. Usually you can safely ignore that fact, due to the way ember’s event system works, but sometimes it can bite you, so keep it in mind. We retrieve our Org with the line App.Org.find(App.get('orgId')). Let’s take a look at what our server returns.

  // Request

  GET /rest/1/orgs/0

  // Response

  {
    "org": {
      "name": "jubarian.org",
      "description": "A non-profit organization of people...",
      "member_ids": [ 0, 1, 2, 3, 4, 5, 6 ]
    },
    "members": [{
      "org_id": 0,
      "name": "Nina",
      "id": 0
    }, {
      "org_id": 0,
      "name": "Kawan",
      "id": 1
  - snip -
    }]
  }

There are a few things to be aware of here. First, we can see that our API puts the payload of the org object into a field called org (singular). This is where ember-data will look for the Org data. If we had made a more general query that returned a list, it would be orgs in the plural form. Second, we are also returning all the members data with the response. This is referred to as side-loading, and it is optional. In our case, since we have the luxury of a very small application with well defined requirements, we know that this is the most efficient way to do things. If we didn’t side-load, ember-data will make another call to retrieve the data when it’s needed.

Notice that the members field is plural, it’s a list, and ids are included in the objects. Also notice the serialization of the data fields. Ember expects all serialized field names to be underscore separated, even though it expects camelcase in the javascript models. It also expects a pluralized member_ids field for the one-to-many relationship, and a singular org_id for the many-to-one relationship.

All of these behaviors can be modified. Ember-data is very customizable, but unless you are dealing with a legacy API, it’s always best to conform with the frameworks expectations.

Ember Data’s Protocol

Let’s give a brief overview of what ember-data expects to send and receive during different transactions. Understanding this protocol will not only help you implement your own persistent backend, but also is invaluable for debugging ember-data errors, which are often the result of some unexpected data. Many of these details can be modified by configuring the RESTAdapter, but again, if possible, it’s better to stick with the standard. Keep in mind that if you have an existing API that is much different, it might be worth writing your own adapter. We won’t cover custom adapters in this post.

GET one

Call
find(:id)
HTTP Method
GET
URL
/<plural model>/:id
Example Request
GET /orgs/0
Example Response
{
  "org": {
    "name": "jubarian.org",
    "description": "A non-profit organization of people...",
    "member_ids": [ 0, 1, 2, 3, 4, 5, 6 ]
  }
}

GET many

Call
find({:attr: :value}
HTTP Method
GET
URL
/<plural model>?:attr=:value
Example Request
GET /orgs?name=jubarian.org
Example Response
{
  "orgs": [
    {
      "id": 0,
      "name": "jubarian.org",
      "description": "A non-profit organization of people...",
      "member_ids": [ 0, 1, 2, 3, 4, 5, 6 ]
    }
  ]
}

With mutations, we need to commit our changes before they will be sent to the server, either through the records transaction or the store. If we change our minds before we commit, we can invoke rollback instead of commit.

POST

Call
var record = Model.createRecord({...});
record.get('store').commit();
HTTP Method
POST
URL
/<plural model>
Example Request
POST /members
Example Body
{"member":{"name":"Billy","org_id":0}} 
    
Example Response
{"member":{"name":"Billy","org_id":0, "id": 7}} 
    

PUT

Call
var record = Model.find(:id);
record.set(:name, :value);
record.get('store').commit();
HTTP Method
PUT
URL
/<plural model>/:id
Example Request
PUT /members/7
Example Body
{"member":{"name":"William","org_id":0}}
Example Response
{"member":{"name":"William","org_id":0}}

One thing to note is that DELETE MUST return valid json, or you will get an error. I consider this a bug, so hopefully it changes in the future.

DELETE

Call
var record = Model.find(:id);
  
record.deleteRecord();
record.get('store').commit();
HTTP Method
DELETE
URL
/<plural model>/:id
Example Request
DELETE /members/7
Example Response
{}

Creating a Team Admin Page

Next, let’s put it all together and build a member management section for our site. First let’s setup the routes. We’ll have three pages; an index, a new member form and an edit member form.

    // app/scripts/main.js

    - snip -
    App.Router.map(function(){
      this.route('about');
      this.resource('members', function() {
        this.route('new');
        this.route('edit', {path: '/edit/:id'});
      });
    });
    - snip -

Now, let’s build the Routes and Controllers.

    // app/scripts/modules/members.js

    App.MembersIndexRoute = Em.Route.extend({
      setupController: function(controller, model) {
        App.Org.find(App.get('orgId')).then(function(org) {
          controller.set('content', org.get('members'))
        });
        controller.set('content', Em.A());
      }
    })

    App.MembersEditRoute = Em.Route.extend({
      serialize: function(obj) {
        return {id: obj.get('id')};
      },
      model: function(params) {
        return App.Member.find(params.id);
      }
    })

    App.MembersNewRoute = Em.Route.extend({
      model: function(params) {
        return Em.Object.create({org: App.Org.find(App.get('orgId'))});
      }
    })

    /*
     * Wrap member model objects in simple interface callable
     * by the view.
     */
    App.MemberController = Em.ObjectController.extend({
      "delete": function() {
        this.get('content').deleteRecord();
        this.get('store').commit();
      },
      edit: function() {
        this.transitionToRoute("members.edit", this);
      },
      save: function() {
        this.get('store').commit();
        this.transitionToRoute("members.index");
      },
      cancel: function() {
        this.get('store.defaultTransaction').rollback();
        this.transitionToRoute("members.index");
      },
      create: function() {
        this.set('content', App.Member.createRecord(this.get('content')));
        this.save();
      },
      cancelNew: function() {
        this.transitionToRoute("members.index");
      }
    })

    App.MembersIndexController = Em.ArrayController.extend({
      itemController: "member"
    })

    App.MembersNewController = App.MemberController.extend({});
    App.MembersEditController = App.MemberController.extend({});

    // app/index.html

    - snip -
    <script src="scripts/modules/members.js"></script>
    - snip -

If you’ll notice, in MembersIndexController we forgo using the model callback in favor of the setupController callback. This is because we are passing in a field of the result of an asynchronous operation. MembersIndexController needs an Array right away to set itself up. If we called something like Members.find({org_id: 0}) then ember-data would have returned an empty array right away and we would have been fine, but I wanted to showcase a situation where the asynchronous nature of ember-data can leak through, so it’s important to be aware of. Once the call has returned, we can safely swap the result into the controllers content field and everything will be updated as expected.

We’ve also introduced a new controller callback serialize, in the MembersEditController. If your Route has parameters, it is important to provide a way to map the object it represents to the URL parameters. This way when we transition to the route and pass an object directly to it, ember can form a linkable URL out of it. Our serialize method maps the id attribute of the model to the id parameter in the edit URL.

Our MembersIndexController introduces the concept of itemController. itemController wraps each item in an ArrayController, making it easier to perform actions on individual items from the view. In our case, it will allow us to easily delete and edit the individual items.

We used inheritance to get all the functionality of MemberController into MembersEditController and MembersNewController. Since we haven’t added any view logic, we are using the default generated views. Let’s finish up by adding templates for our member management pages, and a link from the about page.

    // app/templates/about.handlebars

    - snip -
    <p>
      <a href="#" {{action toggleTeam}}>{{toggleTeamLabel}}</a>
      {{#linkTo members.index}}manage team{{/linkTo}}
    </p>
    - snip -

    // app/templates/members.handlebars

    <p>{{#linkTo "about"}}about{{/linkTo}}</p>
    <p>{{outlet}}</p>

    // app/templates/members/index.handlebars

    <p>List Members</p>
    <ul>
    {{#each controller}}
      <li>
        {{name}} <a href="#" {{action delete}}>delete</a> <a href="#" {{action edit}}>edit</a>
      </li>
    {{/each}}
    </ul>
    {{#linkTo "members.new"}}add member{{/linkTo}}

    // app/templates/members/edit.handlebars

    <p>Edit Member</p>
    <p><label>
      Name
      {{view Em.TextField valueBinding="name"}}
    </label></p>
    <p>
    <a href="#" {{action save}}>save</a>
    <a href="#" {{action cancel}}>cancel</a>
    </p>

    // app/templates/members/new.handlebars

    <p>Create Member</p>
    <p><label>
      Name
      {{view Em.TextField valueBinding="name"}}
    </label></p>
    <p>
    <a href="#" {{action create}}>save</a>
    <a href="#" {{action cancelNew}}>cancel</a>
    </p>

Security

Obviously, if this were a real application we wouldn’t want just anybody to be able to modify our member list. We’d have to implement some security, but that is outside the scope of this series of articles. One thing to be aware of is that your RESTful API is publicly available, so make sure your security is implemented there, and not on the frontend. Your ember app should be aware of security, but your API should enforce it.

Error Handling

Error handling in ember-data leaves much to be desired, and this is where the fact that it’s a beta project is really obvious. It’s possible to build some error handling via callbacks and states on model objects, but it’s difficult and ever changing. Even finding documentation on the relevant callbacks and states is difficult. It usually involves reading the ember-data code base. We’ve chosen to skip it in this article. It would be of little lasting value, since we expect it to mature rather quickly.

Conclusion

I hope this article, and this series has been able to give you a good high level understanding of ember and ember-data and get you quickly started on the right path. Even if you choose not to use ember-data and roll-your-own, I hope this article has given you some ideas on how to build it. This wraps up our series on Ember at Embedly. Now go create something awesome!

Posted 2 weeks ago by dokipen

Today we are unveiling bulk usage tiers across all products. These tiers will provide you with a discounted cost as your use of the service increasesrevolutionary! These changes will be available immediately and automatically occur for anyone using one of our new products.

If you’re getting the news late, we announced Products back in late March as a way to create the best front-end tools and services on the web. One of our main goals with the products was to scale, and pricing seemed like a good place to start.

You can check out the usage tiers and use our handy calculator on each product’s pricing pageDisplayEmbed, and Extract. Here’s a glimpse of the Embed product’s usage tiers:

image

Have any questions? Just give us a jingle: sales@embed.ly. If you haven’t tried any of our products, sign up for our free intro.

Posted 3 weeks ago by artgibby
Tagged:
This week we’re spotlighting recently launched summary.io, and the founder, Sam Sneeling.

Here we go, what is the mission behind summary.io?

Summary.io was built as a proof of a concept that you can summarize a large amount of news in a short period of time. The overall goal for our first version is to enable people to find articles that they want to read. The technology that powers it can really be applied to almost any body of text. We are looking at how summarizing text can make an impact in everyday lives.

How does Embedly help with that mission?

Embedly really helps us look good (by providing images for our news summaries). As of right now, Embedly is used as a fallback, but it works consistently. Really helps overall to have a reliable backend.

What would you like to see next from Embedly?

I think Embedly has a lot to offer. Digging into your docs, your kitchen sink api is really quite incredible. I look forward to seeing that api become more available for a lower cost so that small developers can create really cool stuff.

If you’re interested in using our natural language processing and text extraction tools, check out our new product, Extract.

Sign up and get 5,000 calls per month free.

Posted 3 weeks ago by ninerr