Step 7 - Todos from FoxPro

Everything we've done so far in this tutorial has been done on the client side - the Todo list is managed as an array in memory so every time you refresh the page the list goes back to its initial state. While that works great for the initial demo it's not very useful if the values are not saved.

In this step we'll look at hooking up a couple of very simple REST API endpoints to serve the list and add and delete Todo items to mimic the functionality we have created.

Creating the Server API Methods

So we'll need to create 3 operations in the todoProcess.prg methods on the server to match the client API we've set up so far:

  • Todos - List Todos(Todos.td - GET)
  • Todo - Add a new Todo (Todo.td - PUT)
  • Todo - Delete a Todo (Todo.td - DELETE)

Here's what this code looks like (added to TodoProcess.prg):

************************************************************************
*  ToDos
****************************************
FUNCTION ToDos()

IF !FILE(".\todos.dbf")
	CREATE TABLE TODOS (title c(100), descript M, entered T,completed L)

	INSERT INTO TODOS VALUES ("Load up sailing gear","Load up the car, stock up on food.",DATETIME(),.f.)
	INSERT INTO TODOS VALUES ("Get on the road out East","Get in the car and drive until you find wind",DATETIME(),.f.)
	INSERT INTO TODOS VALUES ("Wait for wind","Arrive on the scene only to find no wind",DATETIME(),.f.)
	INSERT INTO TODOS VALUES ("Pray for wind","Still waiting!",DATETIME(),.F.)
	INSERT INTO TODOS VALUES ("Sail!","Score by hitting surprise frontal band and hit it big!",DATETIME(),.F.)
ENDIF

SELECT title, descript as Description, entered, completed FROM TODOS ;
	 ORDER BY entered ;
	 INTO CURSOR Tquery

RETURN "cursor:TQuery"
ENDFUNC
*   ToDos



************************************************************************
*  ToDo
****************************************
FUNCTION ToDo(loToDo)

*ERROR "We're not ready to accept your ToDo's just yet..."

IF !USED("ToDos")
   USE ToDos IN 0
ENDIF
SELECT ToDos

lcVerb = Request.GetHttpverb()

IF lcVerb = "PUT" OR lcVerb = "POST"
	IF VARTYPE(loTodo) # "O"
	   ERROR "Invalid operation: No To Do Item passed."
	ENDIF


    LOCATE FOR TRIM(title) == loToDo.Title
    IF !FOUND()
		APPEND BLANK
	ENDIF
	GATHER NAME loTodo MEMO	
	
	*** Fix for differing field name
	REPLACE descript WITH loTodo.description
ENDIF

IF lcVerb = "DELETE"
   lcTitle = Request.QueryString("title")
   LOCATE FOR TRIM(title) == lcTitle
   IF !FOUND()
      Response.Status = "404 Not found"
      ERROR "Invalid Todo - can't delete."      
   ENDIF
   
   DELETE FOR TRIM(Title) == lcTitle
   RETURN .t.
ENDIF

RETURN loTodo
*   ToDo

The code is pretty straight forward. The Todos method simply retrieves a cursor and returns that as JSON optionally creating the table if it doesn't exist. The ToDo method is overloaded to handle adding, updating and deleting from a single URL based on the HTTP verb. While you can have separate methods for each of those, it's common practice in REST APIs to overload the nouns (Todo) with multiple actions that can be performed on them (POST,PUT,DELETE).

Let's make sure the server is running and working and then navigate to:

http://localhost/todo/todos.td

to check for the JSON response of customers.

Likewise you can test an update request - here I use West Wind Web Surge to fire the request:

Adding CORS

Our server works but it needs to work with cross-domain access, in order to receive requests from the WebPack dev server that we run the development Web site on. Although it runs locally at http://localhost:3000/ effectively this is a different domain. Browsers prohibit cross domain requests unless the server provides CORS headers that allow it to be accessed by clients for authorized domains.

This is easy to add - if you create a REST service in Web Connection as we did earlier, there's a commented section that can be uncommented in todoProcess.prg in the OnProcessInit() method:

*** Add CORS header to allow cross-site access from other domains/mobile devices on Ajax calls
Response.AppendHeader("Access-Control-Allow-Origin","*")
Response.AppendHeader("Access-Control-Allow-Origin",Request.ServerVariables("HTTP_ORIGIN"))
Response.AppendHeader("Access-Control-Allow-Methods","POST, GET, DELETE, PUT, OPTIONS")
Response.AppendHeader("Access-Control-Allow-Headers","Content-Type, *")

*** Allow cookies and auth headers
Response.AppendHeader("Access-Control-Allow-Credentials","true")
 
*** CORS headers are requested with OPTION by XHR clients. OPTIONS returns no content
lcVerb = Request.GetHttpVerb()
IF (lcVerb == "OPTIONS")
   *** Just exit with CORS headers set
   *** Required to make CORS work from Mobile devices
   RETURN .F.
ENDIF   

This effectively allows any browser client to access the site and is what's required in order to get the WebPack server to connect to our site. Note that eventually you'll likely run the entire site via IIS in which case the above headers are not needed, but if you want external access to the service then these sets of headers are required.

Now we're ready to call the methods from our Angular development environment.


© West Wind Technologies, 1996-2024 • Updated: 09/19/2016
Comment or report problem with topic