Friday, June 20, 2014

Friend Request in JavaScript using Parse.com - Part 3

Now for actions, assuming you added some nice little buttons to those lists.

These are very simplistic, just to show the concept. You would probably want to have some Cloud Code that will verify things in a before-save function. Also all my examples haven't included any error handling but you should add some.

Approve a request

// assuming you saved the FriendRequest.id as maybe an attribute
// data-friend-request-id="xxxx" on the Approve button this code is
// linked to
var friendRequest = new FriendRequest();
friendRequest.id = $(this).data('friendRequestId');
friendRequest.set('status', RequestStatus.approved);
friendRequest.save();

Reject a request

As per Approve, but use
friendRequest.set('status', RequestStatus.rejected);

Un-friend

As per Approve, but use
friendRequest.set('status', RequestStatus.rejected);

Friend Request in JavaScript using Parse.com - Part 2

Continued from Part 1, our next requirement is:

List requests from others waiting for my approve/reject action

// remember our common code, FriendRequest, currentUser
// and RequestStatus are already defined
var pendingRequestQuery = new Parse.Query(FriendRequest);
pendingRequestQuery.equalTo('toUser', currentUser);
pendingRequestQuery.equalTo('status', RequestStatus.requested);

requests.find().then(function (requests) {
  // render them somewhere with Approve/Reject buttons
});
I'll leave it as an exercise for the reader to render the list.

List my friends

var myFriendsQuery1 = new Parse.Query(FriendRequest);
myFriendsQuery1.equalTo('fromUser', currentUser);
myFriendsQuery1.equalTo('status', RequestStatus.approved);

var myFriendsQuery2 = new Parse.Query(FriendRequest);
myFriendsQuery2.equalTo('toUser', currentUser);
myFriendsQuery2.equalTo('status', RequestStatus.approved);

var myFriendsQuery = Parse.Query.or(myFriendsQuery1, myFriendsQuery2);
myFriendsQuery.find().then(function (approvedFriendRequests) {
  // render them
});

List my requests that are pending

var pendingRequestQuery = new Parse.Query(FriendRequest);
pendingRequestQuery.equalTo('fromUser', currentUser);
pendingRequestQuery.equalTo('status', RequestStatus.requested);

pendingRequestQuery.find().then(function (pendingRequests) {
  // render them
});

List my rejections

var rejectedMeQuery = new Parse.Query(FriendRequest);
rejectedMeQuery.equalTo('fromUser', currentUser);
rejectedMeQuery.equalTo('status', RequestStatus.rejected);

rejectedMeQuery.find().then(function (rejectedMeRequests) {
  // render them
});

List requests I have rejected

var rejectedByMeQuery = new Parse.Query(FriendRequest);
rejectedByMeQuery.equalTo('toUser', currentUser);
rejectedByMeQuery.equalTo('status', RequestStatus.rejected);

rejectedByMeQuery.find().then(function (rejectedByMeRequests) {
  // render them
});
In the next part I'll talk about Actions.

Friend Request in JavaScript using Parse.com - Part 1

This is a follow-on from my last post, almost a year ago. Yes, I know, I should post more often, anyway...

First off we need to define some things that are common to all the code blocks:
// fake an enum, allows auto-complete for some editors
var RequestStatus = {
  requested: 'requested', 
  rejected: 'rejected',
  approved: 'approved'
};
// define our Parse Class
var FriendRequest = Parse.Object.extend('FriendRequest');
var currentUser = Parse.User.current();
I will also assume you have jQuery available.

Now lets go through our requirements.

Find a person (highlight existing status)

// for simplicity lets assume we're doing a search for people
// in the same "area" as us, lets also assume you're aware of
// case-sensitive issues and created a "search_Area" property
// forced to lower-case (using a before-save Cloud Function)

var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('search_Area', currentUser.get('search_Area'));

// shared array we'll store details in
var userList = [];
// dictionary we'll use for easier updating
var userDict = [];
userQuery.find().then(function(users) {
  // lets just extract the "id" and "username" first
  for (var i in users) {
    var userInfo = {
      id: users[i].id, 
      name: users[i].get('username')
    });

    userList.push(userInfo);
    userDict[userInfo.id] = userInfo;
  }

  // now we need to attach a status to each one
  var myRequests = new Parse.Query(FriendRequest);
  myRequests.equalTo('fromUser', currentUser);
  myRequests.equalTo('toUser', users);

  var requestsToMe = new Parse.Query(FriendRequest);
  requestsToMe.equalTo('fromUser', users);
  requestsToMe.equalTo('toUser', currentUser);

  var combinedRequests = Parse.Query.or(myRequests, requeststoMe);

  return combinedRequest.find();
}).then(function (requests) {
  for (var i in requests) {
    var request = requests[i];
    var status = request.get('status');

    if (request.get('fromUser').id == currentUser.id) {
      // set status from my perspective
      userDict[request.get('toUser').id].statusMessage = 
        status == RequestStatus.approved ? 'Already my friend' :
        status == RequestStatus.rejected ? 'I rejected them' :
        status == RequestStatus.requested ? 'Waiting for them to reply' :
        'Unknown status';
    } else {
      // set status from the other person's perspective
      userDict[request.get('fromUser').id].statusMessage = 
        status == RequestStatus.approved ? 'Already my friend' :
        status == RequestStatus.rejected ? 'They rejected me' :
        status == RequestStatus.requested ? 'Waiting for me to reply' :
        'Unknown status';
    }
  }

  // at this point you can render the userList
  for (var i in userList) {
    var userInfo = userList[i];
    // assuming you have a container DIV with id="possibleFriends"
    $('#possibleFriends').append(
      '<div data-user-id="' +  userInfo.id + '"'
      + ' class="possibleFriend">'
      + userInfo.userName
      // not all will have a status, so handle that
      + (userInfo.status ? ' - ' + userInfo.status : '')
      + '</div>');
  }
});
With the above you can extend it however you like to allow sorting etc, it is just the bare-bones implementation.

Wednesday, September 25, 2013

A parse.com tutorial - implementing a friends list

I've been playing with Parse in my spare time recently, and a couple of topics seem to come up quite often: relationships and some kind of "friends list" functionality.

A "friends list" is a great example of a many-to-many relationship. Seems to me that a tutorial of how to add a "friends list" would solve both these questions, so that's what I'm doing.

Requirements

First lets think of what a "friends list" should entail:
  • Find a person
  • Add them to my friends list
That's it! We're done, right? Well often you want to allow people to approve/reject being friended. You'll want to have the friendship be both ways (if I add you to my list, then you get to add me to yours). There's all sorts of extra things to consider.

Revised Requirements

Here's an expanded list, lists first:
  • Find a person
    • Highlight if I already have them in my friends list, or have asked
  • List requests from others waiting for my approve/reject action
  • List my friends
  • List my requests that are pending
  • List my rejections
  • List requests I have rejected
Now for actions we want to be able to perform:
  • Request to add someone as a friend
  • Approve a request
  • Reject a request
  • Un-friend
  • Things we could add later:
    • Un-reject (change to approved, in case someone makes a mistake)
    • Re-request (possibly with a time-window to prevent abuse)
We will leave off things like showing how many friends you have in common etc.

Given these requirements, a Parse class like the following should give us what we want:

Class: FriendRequest
RequestFrom (reference: User)
RequestTo (reference: User)
Status (string: requested/approved/rejected/etc)

So, lets see how we can handle the above lists and actions:

Find a person (highlight existing status)

Once your existing "find a person" query is run, you would need an array of User objects. Using this list we could ask for all FriendRequest objects where RequestFrom is in the list or RequestTo is in the list.

List requests from others waiting for my approver/reject action

Query FriendRequest where the current user is the RequestTo and the status is "requested".

List my friends

Query FriendRequest where the current user is RequestFrom or RequestTo and the status is "approved".

List my requests that are pending

Query FriendRequest where the current user is RequestFrom and the status is "requested".

List my rejections

Query FriendRequest where the current user is RequestFrom and the status is "rejected".

List requests I have rejected

Query FriendRequest where the current user is RequestTo and the status is "rejected".

Well, looks like we can handle all the requests, what about the actions?

Request to add someone as a friend

First make sure there's no existing record with the current User and the target User as RequestFrom/RequestTo or the other way around.

If that requirement is met then you just need to create a new FriendRequest object with the following properties:
RequestFrom: current User
RequestTo: target User
Status: requested

Approve a request

Change the Status from "requested" to "approved".

Reject a request

Change the Status from "requested" to "rejected".

Un-friend

Change the Status from "approved" to "rejected".

Summary

In theory it looks like everything is covered, though I welcome comments and feedback if you think I'm missed anything or made a mistake.

I'll post some sample code (JavaScript and maybe some Objective-C too) once I get my code formatting working again.

Things I'm playing with now

Wow, it's been a while since I've updated this blog. Looks like my code-formatting broke too.

Anyway, lots of things have changed, but here's what I am looking at recently:
  • Parse.com, a data backend with SDKs for all major platforms: JavaScript, iOS, Andriod, .NET as well as REST
  • LINQ, specifically PredicateBuilder that lets you build OR logic, as well as building expressions and having them translated into SQL calls
I'll see if I can post some tutorials on using Parse some time soon.

Thursday, October 29, 2009

Running Windows 7 in a VM on OS X 10.6

Anyone that’s worked with me recently will know that I’m a “switcher”, someone who has changed from being a Mac hater / Windows lover to a more realistic lover of what’s good in both OS’s (it helps that Mac released OS X, as most of my pain was with OS 7-9 clients on a Windows NT network). That said all the computer in my house are Macs, except for an old Toshiba T200 TabletPC that my wife uses to play games with the kids. This gives me the ability to run any operating system I like on all the computers.

It should come as no surprise then that I eagerly tried Windows 7 back when the first beta was made available to the public. I chose to run it in Boot Camp, mostly because I was testing how well my games ran, since I had been running them in XP. When I was doing “serious” stuff (Visual Studio 2008, SQL etc) I mostly ran in a VM in Mac mode, as it gave me greater flexibility.

After my terrible experience with Vista (twice it became unrecoverable, causing me a lot of problems) I went cautiously, but was pleasantly surprised to find that Windows 7 (even as Beta and then RC) was pretty stable, quite responsive, and ran everything rather well.

My only real pain point was having to reboot into Boot Camp for anything that required decent graphics, and to get the full Aero experience.

To my great delight VM Ware and Parallels recently release new versions of their VM tools, I quickly tried both, as I have purchased previous versions of both and found each to have nice features.

Parallels 4

I was disappointed to discover no DirectX support in the latest offering, unless I missed the feature somewhere. That said, the features continue to improve and it feels more solid.

VM Ware Fusion 3

This release is huge, they’ve changed a lot and I’m quite impressed. I’ve so far tried it on a 2009 Mac Book Pro 13”, and a iMac (late 2007 model I think, with Core 2 Extreme 2.8). Both ran it quite well, Windows 7 works with Aero, and yes even games seem to work well, although given that those two computers have limited graphics power to start with I wasn’t expecting much.

Once I can convince my wife to get off my Mac Pro for long enough I’ll test the Direct 3D support on there, since with 8 cores and 6Gb RAM with a 9800GT card I expect to be able to get good performance even in a VM.

Wednesday, October 14, 2009

First week at a new job

Well I’ve started a new contract this week, but this time I’m not inflicted with draconian web filtering. Maybe now I’ll be able to do some more blogging, posting little tid-bits of information that seem interesting and get back in touch with some of my peers online.

I was with ABB Grain (now a yet-to-be-named part of Viterra, perhaps Viterra Australia?) for around 15 months, in that time I assembled a team of great developers and as a team we produced a great product that our users love. Thanks to everyone there that made those 15 months so memorable for me.

I’ve discovered that I can even run MSN Messenger now, but we’re under a pretty tight deadline so I doubt I’ll have time to chat or answer questions for the next couple of weeks, so I won’t bother signing in until things calm down a bit.

Oh, Ben Laan, this means you have to do your blog post now :P