Sunday, July 31, 2016

An Example of Using Dart - forcemvc, MongoDB, and objectory, part 2

/lib/model.dart

part of controllers;

class Person extends PersistentObject {
//  String get collectionName => "Name";
  String get firstName => getProperty('first_name');
  set firstName(String value) => setProperty('first_name', value);
  String get lastName => getProperty('last_name');
  set lastName(String value) => setProperty('last_name', value);
  String toString() => "$firstName $lastName";
  Future<bool> isDuplicate() {
    Completer completer = new Completer();
    bool answer;
    objectory.initDomainModel().then((success) {
      objectory[Person].find(where.eq('first_name', this.firstName)
          .and(where.eq('last_name', this.lastName))).then((matches) {
        if (matches.length > 0) {
          answer = true;
        } else {
          answer = false;
        }
        completer.complete(answer);
      });
    });
    return completer.future;
  }
}

add(Person person) {
/*  objectory =
  new ObjectoryDirectConnectionImpl('mongodb://127.0.0.1/forceobject2',
      () {    objectory.registerClass(Person, () => new Person(),
        () => new List<Person>());  }, false); */
//  init();
  objectory.initDomainModel().then((_) {
    objectory.save(person);
  });
}


Future<Person> newPerson(String first, String last) {
  Completer completer = new Completer();
/*  objectory =
  new ObjectoryDirectConnectionImpl('mongodb://127.0.0.1/forceobject2',
      () =>      objectory.registerClass(Person, () => new Person(),
          () => new List<Person>()), false);
          */  //init();
  objectory.initDomainModel().then((_) {
    Person person = new Person()
      ..firstName = first
      ..lastName = last;
    completer.complete(person);
    //   objectory.close();
  });
  return completer.future;
}

Future<List<Person>> allPeople() {
  Completer completer = new Completer();
/*  objectory =
  new ObjectoryDirectConnectionImpl('mongodb://127.0.0.1/forceobject2',
      () =>      objectory.registerClass(Person, () => new Person(),
          () => new List<Person>()), false);
          */
//  List<Person> names = new List();
//  init();
  objectory.initDomainModel().then((success) =>
      objectory[Person].find().then((List<Person> people){
        completer.complete(people);
//        objectory.close();
      })
  );
  return completer.future;
}

Future<bool> isDuplicate(Person person) {
  Completer completer = new Completer();
  bool answer;
  objectory.initDomainModel().then((success) {
    objectory[Person].find(where.eq('first_name', person.firstName)
        .and(where.eq('last_name', person.lastName))).then((matches) {
      if (matches.length > 0) {
        answer = true;
      } else {
        answer = false;
      }
      completer.complete(answer);
    });
  });
  return completer.future;
}


/lib/controllers.dart

library controllers;
import 'package:objectory/objectory_console.dart'
;import 'package:forcemvc/force_mvc.dart';
import 'dart:async';
part 'model.dart';

init() {
  objectory = new ObjectoryDirectConnectionImpl(
      'mongodb://127.0.0.1/forceobjectory2', () =>
      objectory.registerClass(
          Person, () => new Person(), () => new List<Person>()), false);
}


@Controller
class MainControllers {
  @RequestMapping(value: '/', method: RequestMethod.GET)
  String index(ForceRequest req, Model model) {
    return 'index';
  }

  @RequestMapping(value: '/verify', method: RequestMethod.POST)
  Future<String> verify(ForceRequest req, Model model) {
    req.getPostParams().then((Map params) {
      init();
      String first = params['first_name'];
      String last = params['last_name'];
      newPerson(first, last).then((Person person) {
        model.addAttribute("person", person);
        //      return isDuplicate(person).then((bool match) {
        return person.isDuplicate().then((bool match) {
          if (!match) {
            return person.save();
          }
        });
      }).then((_) {
        return allPeople().then((List people) =>
            model.addAttribute("people", people));
      }).then((_) {
        model.addAttribute("message", "It works!");
        req.async('verify');
      });
    });
    return req.asyncFuture;
  }
}


Saturday, July 30, 2016

An Example of Using Dart - forcemvc, MongoDB, and objectory, part 1

This app is a simple idea... ask the user for his name, then check it against your records. If nothing on file matches, that name gets added to the database. A list of all the saved names also appears. I decided to use Ojectory because it works with data objects more directly as objects than mongo_dart does.

Since we're using Dart, the first step is writing the pubspec.yaml file. This file configures the app's dependencies on third-party packages. For the sake of sanity, we will specify exactly which versions to use. We only need two external packages, objectory for database access and forcemvc for a web architecture. What we need is something like

/pubspec.yaml


name: ForceMVC_Objectory2
version: 0.0.1
description: A simple console application.
dependencies:
  objectory: 0.3.21
  forcemvc: 0.8.5

You can use any project name and description you like, but you'll need the rest as is. The next step is to make a /bin directory in the root of our project. All that we will write there is the main.dart file. That file contains the main function, which is always the entry point. There we create a WebApplication object to configure and start the web server. main() also tells the user what web address will be used; however, the print statement has no actual affect on it.

/bin/main.dart


library ForceMVC_Objectory2;
import 'package:forcemvc/force_mvc.dart';
import '../lib/controllers.dart';

main(List<String> args) {
  print('Opening localhost:8080');
  WebApplication application = new WebApplication();
  application.start();
}

We will be using the default settings. That means that the views will go in a /views directory. We put two files there: index.html and verify.html. index.html has the form, and verify.html shows the results.


/views/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Force Objectory 2</title>
</head>
<body>
<form action="/verify" method="post">
    <fieldset>
        <label for="first_name">First name: </label>
        <input id="first_name" name="first_name" type="text" required>
    </fieldset>
    <fieldset>
        <label for="last_name">Last name: </label>
        <input id="last_name" name="last_name" type="text" required>
    </fieldset>
    <input id="btn" type="submit" value="Continue">
</form>

</body>
</html>
 

/views/verify.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Name List</title>
</head>
<body>
<p>{{message}}</p>
<p>Tried to add {{person}}</p>
<ul>
    {{# people}}
    <li>{{firstName}} {{lastName}}</li>
    {{/ people}}
</ul>

{{^ people}}
<h3>Empty List</h3>
{{/ people}}
</body>
</html>


{{person}} echoes the name the user entered. {{people}} is an updated list of all the names on file. "#" generates a list, and "^" means "if the list is empty". In the interest of space, the controllers and model code will be in the next post.

Tuesday, March 29, 2016

Using Dart (dartlang) as an HTML client and Go (golang) as a server

Hi all! I'm back again. This blog might be short on explanation, but basically it gives an example of mixing Google technologies. What this example does is serve MP3 files from the server to the client, which get played on the client's speakers.

First, the server. For that, you'll need Go (https://golang.org/) and a folder to put the Go code and the MP3 files. The only Go file you need is main.go, as follows:

package main      

import (
    "log"
    "net/http"
    "strings"
//    "io/ioutil"
)

func main() {
    port := "localhost:8000"
    log.Println("Opening port", port)
    http.HandleFunc("/music/", getMusic)
    http.ListenAndServe(port, nil)
    }
    


func getMusic(w http.ResponseWriter, r *http.Request)  {
    w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE")
    url := r.URL.Path
    urlParts := strings.Split(url, "/")
    log.Println(urlParts[2])
    fileName := urlParts[2] + ".mp3"
    log.Println("Sending file", fileName)
    http.ServeFile(w, r, fileName)
      
}

What this code does is listen for a REST "get" request for "localhost:8000/music/". The ending "/" is needed so that the sender can hitch the file name to the URL. The code attaches CORS headers to the response, as this is a cross-origin situation. The URL gets parsed, then the resulting file name gets ".mp3" attached. That file then gets send back to the client with the headers.

The client is a bit more complex. For the client, you need a pubspec.yaml file, a /web subfolder, and Dart (https://www.dartlang.org/).

Your pubspec.yaml should look like:

name: DartMusicPlayer2
author: Ross Albertson <magnusthegood@yahoo.com>
description: A Dart music player using classes

dependencies:
  browser: any
  dart_to_js_script_rewriter: any
  
transformers:
- dart_to_js_script_rewriter   

Use any project name and description you wish, and replace the author with your name and e-mail address.  You'll need the dependencies and transformers to translate your Dart and HTML correctly.

The HTML (/web/index.html) is pretty trivial:

<!DOCTYPE html>
<html>
    <head>
        <title>Dart/Go Music Player 2</title>
        <script type="application/dart" src="main.dart"></script>
        <script src="packages/browser/dart.js"></script>
    </head>
    <body>
        <p>"July" by Weekend <button id="july">Play/Stop</button></p>
        <p>"Drive Blind" by Ride <button id="drive-blind">Play/Stop</button></p>
    </body>
</html> 

The <button> id's and the plain text can be substituted by any mp3 song you have. Lastly, there is the /web/main.dart, as follows:

import 'dart:html';
import 'dart:web_audio';

AudioContext audioContext;

void main() {
  audioContext = new AudioContext();
  new Song(querySelector("#july") as ButtonElement, "July");
  new Song(querySelector("#drive-blind") as ButtonElement, "Drive_Blind");
}

class Song {
  ButtonElement button;
  bool _playing = false;
  // AudioContext _audioContext;
  AudioBufferSourceNode _source;
  String title;

  Song(this.button, this.title) {
    // _audioContext = new AudioContext();

    button..onClick.listen((e) => _toggle());
  }

  _toggle() {
    _playing = !_playing;
    _playing ? _start() : _stop();
  }

  _start() {
    return HttpRequest
        .request("http://localhost:8000/music/$title",
            responseType: "arraybuffer")
        .then((HttpRequest httpRequest) {
      return audioContext
          .decodeAudioData(httpRequest.response)
          .then((AudioBuffer buffer) {
        _source = audioContext.createBufferSource();
        _source.buffer = buffer;
        _source.connectNode(audioContext.destination);
        _source.start(0);
      });
    });
  }

  _stop() {
    _source.stop(0);
  }
}

A global AudioContext is needed in any event. The Song class just requires a <button> element and a file name, minus the file extension. If you want to use multiple file formats, just change the Go so that is uses the URL variable as is, and send the whole file name to the Song constructor. I combined several online Dart examples to come up with this code. The "decodeAudioData" is needed to make the client work with more browsers. This object-oriented approach allows you to put your songs in separate threads. It is also needed to be able to play music after stopping a song; I discovered that quirk in an earlier attempt. You might need to compile the Dart in "debug" mode (pub build --mode debug) and use those /build/web files in production. I found this necessary when using IIS. One last thing... make sure your Dart HTTP request matches with your Go URL and port number.



Thursday, September 4, 2014

Example of Beegae - Part 3

Now we're finally getting to the code. This app maintains a list of names, so naturally I made a struct called "Name". Here is models/model.go:

  // model
package models

import (
    "appengine"
    "appengine/datastore"
)

type Name struct {
    FirstName, LastName string
}

const COLLECTION = "name"

func (this Name) String() string {
    return this.FirstName + " " + this.LastName
}

func (name Name) GetMatchCount(c appengine.Context) (count int, err error) {
    //    c := appengine.NewContext(rq)
    q := datastore.NewQuery(COLLECTION).Filter("FirstName =", name.FirstName).
        Filter("LastName =", name.LastName)
    var results []Name
    _, err = q.GetAll(c, &results)
    if err != nil {
        count = -1
    } else {
        count = len(results)
    }
    return
}

func (this Name) Add(c appengine.Context) error {
    key := datastore.NewIncompleteKey(c, COLLECTION, nil)
    _, err := datastore.Put(c, key, &this)
    return err
}

func GetAllNames(c appengine.Context) (names []Name, err error) {
    q := datastore.NewQuery(COLLECTION)
    _, err = q.GetAll(c, &names)
    return
}

As you can see, I'm using Datastore. The COLLECTION constant sets the data type string. The GetMatchCount function queries the database for names matching the incoming name, then calculates the number of matches and returns the total. The GetAll API function retrieves all of the documents matching the query. NewIncompleteKey generates a document key. Lastly, the String function tells Go how to convert a Name to a string. We will see how that is used soon. The Contexts determine which database gets accessed. 

Now we'll get to the controllers. Here is controllers/default.go:

package controllers
import (
    "fmt"
    "github.com/astaxie/beegae"
    "log"
    "models"
)

type MainController struct {
    beegae.Controller
}

func (this *MainController) Get() {
    this.Data["Title"] = "Beegae Example #1"
    this.TplNames = "form.tpl"
}

type VerifyController MainController

func (this *VerifyController) Post() {
    context := this.AppEngineCtx
    first := this.GetString("first")
    last := this.GetString("last")
    name := models.Name{first, last}
    count, err := name.GetMatchCount(context)
    message := fmt.Sprintf("%s %d", name, count)
    if err != nil {
        message = err.Error()
    }
    if count == 0 {
        if err := name.Add(context); err != nil {
            message = err.Error()
        }
    }
    this.Data["Title"] = "Beegae Example #1"
    this.Data["Message"] = message
    this.TplNames = "verify.tpl"
}

type ListController struct {
    beegae.Controller
}

func (this *ListController) Get() {
    context := this.AppEngineCtx
    //    var names []models.Name
    names, err := models.GetAllNames(context)
    if err != nil {
        log.Fatal(err.Error())\
    }
    this.Data["Title"] = "Beegae Example #1"
    this.Data["Names"] = names
    this.TplNames = "list.tpl"
}

GetString retrieves the form variable with the given name and casts it as a string. As you can see, the Sprintf function refers to a variable of type "Name" as a string. models.String tells Go how to implicitly cast that structure. In Beegae, constructors are actually types, which are used as receivers for the appropriate REST function. 
The views are pretty much trivial except for list.tpl. Here that is:

  
<!DOCTYPE html>

<html>
      <head>
        <title>{{.Title}}</title>
        <link rel="stylesheet" href="/static/css/style.css" type="text/css" />

    </head>
    <body>
        <h3>Updated List</h3>
        <ul>
            {{range $v := .Names}}
                <li>{{$v}}</li>
            {{end}}
        </ul>
    </body>
</html>        

The "range" keyword loops through the list of names and assigns each entry in turn to $v. As you can see, the data is treated like a string. The routing is fairly simple:

package routers
import (
    "beegaenames/controllers"
    "github.com/astaxie/beegae"
)

func init() {
    beegae.Router("/", &controllers.MainController{})
    beegae.Router("/verify", &controllers.VerifyController{})
    beegae.Router("/list", &controllers.ListController{})
}

That is routers/myrouter.go. Each Router has a URL and a variable. As you saw in earlier code, those variables are usually structs that contain a member of type beegae.Controller. The last thing we'll look at is how I deployed this app.

Friday, August 29, 2014

Example of Beegae - Part 2

The next step was to establish and configure my project. Before we get into that, I wanted a couple of Go packages to make things easier, namely github.com/astaxie/beego and github.com/beego/bee. I compiled the second package into the "bee" command.
I already had a directory in my root called "goprojects2". I created a "src" subdirectory. Then I went into the SDK command prompt and issued:

set gopath=%gopath%;c:\goprojects2

then went to the goprpjects2 directory and entered

bee new beegaenames

The "bee" command created a scaffold for my project. However, I had to convert the project from Beego to Beegae. I went into main.go (in goprojects2/src/beegaenames), and replaced "beego" with "beegae" .  I also went into controllers/default.go and routers/router.go, replacing "beego" with "beegae".
I needed to do one last thing before writing Go, namely create index.yaml on the same level as main.go. This file indexed my database. Here are the contents:

indexes:

- kind: Name
  ancestor: no
  properties:
  - name: FirstName
    direction: asc
  - name: LastName
    direction: asc

In the next installment, we'll get into the actual code.

Tuesday, August 26, 2014

Example of Beegae (Beego for App Engine) - Part 1

Sorry I've been gone for so long! I recently created a simple web form using Beegae. The setup was rather involved, but worth it. I will assume you have Git and Python 2.x already installed. I needed to install Go (http://golang.org/) and Google Cloud SDK (https://cloud.google.com/developers/ ; click "Download the SDK" for instructions). I used Datastore, the original NoSQL native to App Engine.

The next thing I had to do was install the Go SDK. To to that, I went into the Google Cloud SDK console window, and issued

gcloud components update gae-go

If you're using the Windows version, there might be a bug in goapp.bat. You will probably find it at

C:\Program Files\Google\Cloud SDK\google-cloud-sdk\platform\google_appengine

I issued the following to retrieve the Beegae Go package:

goapp get github.com/astaxie/beegae

and got this error...

'C:\Program' is not recognized as an internal or external command,
operable program or batch file.

I fixed the problem by these steps:

  1. Open Notepad as Administrator
  2. Open goapp.bat
  3. Put the %GOROOT%\bin\%EXENAME% line, along with all the parameters, on a new line
  4. Put quotation marks (") around %GOROOT%\bin\%EXENAME%
  5. Save (make sure NOT as a .TXT file) and Exit
If you're a bit squeamish, Copy goapp.bat as goapp.bat.old first. I reissued the above "goapp" command, and this time it ran. In the next installment, I'll get into how I configured this project.



Monday, February 10, 2014

After a long "vacation"...

Hi all!

I'm sorry I left you all in the lurch. I have been busy writing code, and you can see some of it at https://github.com/ozonesurfer . As a warning, Tiedot is a work in progress, and if you try to use my current code with Tiedot 2.0, it won't work.

That being said, I thought I would put my  2 cents in on some of the current "wars of religion". Like Tiedot, the tech world is in transition. Transitions are messy things; you have to phase the old out gracefully, and allow the new to mature. People are ditching their landlines for cell phones. People now text instead of call, and e-mail/post instead of writing letters. I'm not going to say one set of technology is better than others. They're just different, each with their benefits and flaws.

As for what I've seen, one of my friends bought a Chromebook and a Windows Phone, and she's perfectly happy with both. Another friend feels incapable of using Windows. She has an old iMac, and plans to eventually get a new one.

 As for me, I bought this Vista HP laptop in 2009; now I use Windows 8.1 on the same machine. My only real problem is my graphics chips sometimes struggle with video. And yes, I once used Windows 7 too. The only real complaint I have with Windows 8.x is the lack of built-in instructions. Despite that shortcoming, I leveraged my knowledge of previous versions and my brains to figure out how to use it. I demoed this knowledge to a couple friends, and not only did they love what they saw, one experimented in front of me and we learned more features together... another later bought a Windows 8 phone.

I consider Windows 8.x as a product of the transition from computers as they were once known to tablets/smartphones. You can't please everyone, but programmers (like me) are stuck with having to try. Some people think change is bad (or at least too inconvenient), and others think change doesn't happen fast enough. I just work with what's available, and I try to keep an open mind to the new.

I think that's embodied in my current software stack: Windows, document-based NoSQL, and Go. I can hear you say "Whrere is the web server? What? No Apache?".  As for Apache or IIS, with Go I don't really need them. The Go framework comes with the "net/http" package, which has the ListenAndServe function. I can specify any web address/port I have permission to manipulate as a parameter.

I'm not saying LAMP is bad or useless. If you know what you're doing, it gets the job done. And yes, I've used JavaScript (namely jQuery) in Go websites. And with Gorilla's websockets, I can broadcast user input in a chatroom website, or even manipulate my websites using the command line. If anyone knows how to do the latter using something else, I'd love to read that code. However, the command line will have to be read, and the entered data be used by the website,while the website is running...no cheating! E-mail it to me at ross.albertson@yahoo.com.      

I haven't seen screenshots of Windows 8.1 Update, but I'm not freaking out about it. I figured out how to use 8.0 and 8.1, and with God's help I'll learn the next version. And I'm not married to Go. I plan to learn Java 8 (or try to) fairly soon. If you stop learning, you stop growing as a person. Besides, to me a technological occupation means signing up to be a professional student to some degree. One last thing... one's belief in a higher power is sacred, but no technology is... if something you created can't be changed, it's broken.