Using Angular Rapydly

RapydScript with AngularJS I’ve been asked a few times if RapydScript works with Angular. The answer is the same as with any other JavaScript framework/library/etc. – of course! RapydScript takes the same approach to compilation/abstraction as CoffeeScript, so there really isn’t anything JavaScript can do that RapydScript can’t. Salvatore has already proven this with multiple demos using frameworks I haven’t even thought of. I urge any developer with similar questions to just try it out, it’s easier (and more fun) to spend an hour building an app than hesitating about it for weeks.

With that said, I decided to put together this tutorial by cloning the 5 examples shown here via RapydScript. I suggest you read the original article before this post. I should also mention that I myself have not used AngularJS before, and decided to learn it through these examples as well. I was a bit disappointed to see that a big chunk of AngularJS is handled magically from their html templates rather than JavaScript itself. This means that there is very little actual JavaScript (and hence RapydScript) used. As far as html itself, you could either write it directly, or use RapydML or Jade (both will have similar syntax). For the purposes of this post, I will leave css code alone, although you can easily convert it to sass as well. Let’s get started.

Setup (Only Needed for RapydML)

Looking at the first example, we immediately notice Django-like variables in the html code. That’s a sign that we may want to use template engines in RapydML. So let’s slap one together. Glancing through the examples we see that most AngularJS variables follow this format:

{{var}}

Doesn’t look like they’re doing anything fancy, so let’s create a very simple template engine in a new pyml file:

angular = TemplateEngine('{%s}')
angular.js = create('{%s}')

I should also mention that you can use AngularJS from RapydML directly, without creating a template engine, I chose to do so for clarity. But nothing prevents you from writing this:

div:
    'This is an angular var: {{myvar}}'

Example 1 (Navigation Menu)

This one doesn’t require any JavaScript. We’ll just need to slap together a RapydML template, leveraging the 2-line template engine we just wrote:

import angular

div(id="main", ng-app):
    nav(class="angular.js(active)", ng-click="\$event.preventDefault()"):
        for $name in [home, projects, services, contact]:
            a(href='#', class="$name", ng-click="active='$name'"):
                python.str.title('$name')

    p(ng-hide='active'):
        'Please click a menu item'
    p(ng-show='active'):
        'You choose'
        b:
            "angular.js(active)"

That’s it, compile it and plug into AngularJS. You will need the most recent version of RapydML, older versions did not handle template engine calls within strings, the new one handles them in double-quoted strings and ignores them in single-quoted strings.

Example 2 (Inline Editor)

As before, we start with the RapydML code:

import angular

div(id='main', ng-app, ng-controller='InlineEditorController', ng-click='hideTooltip()'):
    div(class='tooltip', ng-click='\$event.stopPropagation()', ng-show='showtooltip'):
        input(type='text', ng-model='value')
    p(ng-click='toggleTooltip(\$event)'):
        "angular.js(value)"

Next, let’s add some RapydScript:

def InlineEditorController($scope):
    $scope.showtooltip = False
    $scope.value = 'Edit me.'

    $scope.hideTooltip = def():
        $scope.showtooltip = False

    $scope.toggleTooltip = def(e):
        e.stopPropagation()
        $scope.showtooltip = not $scope.showtooltip

Once again, after compiling the first block of code via RapydML and second via RapydScript, we’ll have a fully-working example.

Example 3 (Order Form)

In this example, we see a new way of hooking into AngularJS. Note the {active: service.active} class tag. We could either reference is in the code verbatim (since it is quoted), or add a new method to the angular template engine for consistency:

angular.active = create('active: %s')

We can now continue with the 3rd example, first the RapydML code:

import angular

form(ng-app, ng-controller='OrderFormController'):
    h1:
        'Services'
    ul:
        li(ng-repeat='service in services', ng-click='toggleActive(service)', ng-class="angular.active(service.active)")
        "angular.js(service.name)"
        span:
            "angular.js(service.price | currency)"

    div(class='total'):
        'Total: '
        span:
            "angular.js(total() | currency)"

Finally, let’s complement it with RapydScript:

def OrderFormController($scope):
    $scope.services = [
        {
            name: 'Web Development',
            price: 300,
            active: True
        }, {
            name: 'Design',
            price: 400,
            active: False
        }, {
            name: 'Integration',
            price: 250,
            active: False
        }, {
            name: 'Training',
            price: 220,
            active: False
        }
    ]

    $scope.toggleActive = def(s):
        s.active = not s.active

    $scope.total = def():
        total = 0
        angular.forEach($scope,services, def(s):
            nonlocal total
            if s.active:
                total += s.price
        )
        return total

As usual, looks similar to the JavaScript version. RapydScript did prevent us from shooting ourselves in the foot by accidentally declaring total as global, something you’d need to be careful about in native JavaScript version.

Example 4 (Instant Search)

For this example, I’ll actually show alternative RapydML code, that doesn’t use our template engine:

div(ng-app='instantSearch', ng-controller='InstantSearchController'):
    div(class='bar'):
        input(type='text', ng-model='searchString', placeholder='Enter your search terms')

    ul:
        li(ng-repeat='i in items | searchFor:searchString')
            a(href='{{i.url}}'):
                img(ng-src='{{i.image}}')
            p:
                '{{i.title}}'

That was even easier than the template engine version, I think I’ll stick to this for my next example as well. Now the RapydScript portion:

app = angular.module('instantSearch', [])
app.filter('searchFor', def():
    return def(arr, searchString):
        if not searchString:
            return arr

        result = []
        searchString = searchString.toLowerCase()
        angular.forEach(arr, def(item):
            if item.title.toLowerCase().indexOf(searchString) != -1:
                result.push(item)
        )
        return result
)

def InstantSearchController($scope):
    $scope.items = [
        {
            url: 'http://tutorialzine.com/2013/07/50-must-have-plugins-for-extending-twitter-bootstrap/',
            title: '50 Must-have plugins for extending Twitter Bootstrap',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/07/featured_4-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/08/simple-registration-system-php-mysql/',
            title: 'Making a Super Simple Registration System With PHP and MySQL',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/08/simple_registration_system-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/08/slideout-footer-css/',
            title: 'Create a slide-out footer with this neat z-index trick',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/08/slide-out-footer-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/06/digital-clock/',
            title: 'How to Make a Digital Clock with jQuery and CSS3',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/06/digital_clock-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/05/diagonal-fade-gallery/',
            title: 'Smooth Diagonal Fade Gallery with CSS3 Transitions',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/05/featured-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/05/mini-ajax-file-upload-form/',
            title: 'Mini AJAX File Upload Form',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/05/ajax-file-upload-form-100x100.jpg'
        }, {
            url: 'http://tutorialzine.com/2013/04/services-chooser-backbone-js/',
            title: 'Your First Backbone.js App – Service Chooser',
            image: 'http://cdn.tutorialzine.com/wp-content/uploads/2013/04/service_chooser_form-100x100.jpg'
        }
    ]

On to the last example.

Example 5 (Switchable Grid)

First the RapydML code, as usual:

def layout($type, $imgtype):
    ul(ng-show="layout == '$type'", class="$type"):
        li(ng-repeat='p in pics'):
            a(href='{{p.link}}', target='_blank'):
                img(ng-src="{{p.images.$imgtype.url}}")

div(ng-app='switchableGrid', ng-controller='SwitchableGridController'):
    div(class='bar'):
        for $icon in [list, grid]:
            a(class='$icon-icon', ng-class="{active: layout == '$icon'}", ng-click="layout = '$icon'")
    layout(list, low_resolution)
    layout(grid, thumbnail)
            p:
                '{{p.caption.text}}'

Notice that I chose to be a little more DRY and create an extra function, you don’t have to do that if you believe it hurts readability. Next, the RapydScript code:

app = angular.module('switchableGrid', ['ngResource'])
app.factory('instagram', def($resource):
    return {
        fetchPopular: def(callback):
            api = $resource('https://api.instagram.com/v1/media/popular?client_id=:client_id&callback=JSON_CALLBACK', {
                client_id: '642176ece1e7445e99244cec26f4de1f'
            }, {
                fetch: { method: 'JSONP' }
            })

            api.fetch(def(response):
                callback(response.data)
            )
    }
)

def SwitchableGridController($scope, instagram):
    $scope.layout = 'grid'
    $scope.pics = []
    instagram.fetchPopular(def(data):
        $scope.pics = data
    )

Conclusion

While the RapydScript code above might be a bit cleaner than the original JavaScript code, it’s hard to argue that significant value was gained from porting it. The main benefit of RapydScript comes into play when building larger-scale applications that leverage classes and use numerous variables. Such code increases the chances of shooting yourself in the foot or getting confused when dealing with native JavaScript. Indeed, if you look at some of the jQuery plugins, you’ll see that the code itself seems unmaintainable. After a few layers of anonymous functions it’s hard to figure out what’s being passed around where. AngularJS keeps the code short by handling a lot of the logic for you on the background, RapydScript allows you to keep your own logic readable as it grows. No matter how awesome a framework is, eventually you’ll be writing a lot of your own logic as well.