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.

Why GPL?

Recently I received an email suggesting that I change the license for RapydScript to something other than GPL. I understand that no matter how powerful, a language is useless if no one wants to use it. That’s why it makes sense not to keep it proprietary, it might have worked for Oracle back in 1977, but it will no longer work today. You might be able to convince corporations to use your platform, but not people working on hobby projects. Likewise, the GPL license might scare those who intend to write commercial software. This is not my intent, I do not wish to hurt either group, which is why the RapydScript libraries are already licensed under Apache license. Part of the fear of the GPL license comes from misunderstanding its terms (especially by those not used to dealing with open-source). The work created using a product under GPL license is not itself subject to GPL license. This is important, GPL is not a cancer that keeps spreading through your tools to your product. Rather, GPL is a way to protect your own work from getting stolen and repackaged as someone else’s for profit (i.e. Cedega building upon Wine). Basically, GPL is a way to protect open-source work from plagiarism.

This protection becomes even more important when the plagiarist has the ability to hurt your project in some way. He could steal your product and then damage your ability to work on your own branch of the it in an attempt to get rid of the competition. For example, imagine that a fictional ACME Corporation writes an operating system. People try it out, they like it, and soon all devices end up running it. Years go by, ACME becomes rich, and many of the devices evolve. Instead of rewriting the OS for new devices, however, ACME decides to keep patching the original OS. They also realize that they could outsource part of the development to further cut costs. Eventually ACME OS code becomes an unmaintanable mess, and the software itself becomes buggy. Then a few guys, frustrated by the bugs in ACME OS, develop an alternative operating system in the basement of their home, call it FREE OS, and release it as open-source. FREE OS immediately becomes popular. ACME, noticing a loss in its profits, decides to repackage FREE OS as a new version of their own. This in itself might hurt FREE OS by stealing its users (and removing the incentive for developers to work on it). But even if FREE OS already got enough traction such that people stick with it, ACME, having much bigger pockets, could go to hardware manufacturers and pay them to add DRM capability using piracy prevention as an excuse (ironically, piracy is exactly what ACME is doing). ACME could then add a proprietary driver to their OS that is able to play files encrypted with this DRM.

Fortunately, RapydScript is not an OS, and as a language it’s not in danger of being affected by a DRM. That doesn’t mean it can’t be ruined by a corporation, however. The compiler aims to generate fast, lightweight code, compatible with various JavaScript obfuscation mechanisms (it should in theory be compatible with advanced mode of Google Closure, although I haven’t tested this). In fact, the new version of Grafpad itself uses an obfuscator based on RapydScript code (which I haven’t released yet) in combination with UglifyJS. The only thing preventing a 3rd party from coming in and selling their own proprietary obfuscator/optimizer based on RapydScript code without contributing anything back is the GPL license of the compiler. This in itself would not actually hurt the language, but if they start breaking backwards compatibility with RapydScript, that could in fact hurt the community. Once the RapydScript community grows and the language gains some initial popularity, this will not be as much of an issue anymore (the obfuscator will be unlikely to gain traction without full support for RapydScript). At that time I won’t mind changing the license to Apache for the entire project.

I’m not strongly attached to the GPL license, and the libraries are already licensed under Apache, allowing companies to build their own private implementations on top of them. Only the compiler itself is licensed under GPL, and I want to encourage that code to stay open to benefit everyone. GPL seemed like the right license for the job. So far the arguments I hear against GPL from other developers are either due to it being used in the wrong places (the APIs/libraries) or unfounded paranoia due to misunderstanding how the license works (people assuming that work created via GPLed product is subject to GPL as well).

Eventually, I do plan to release both, RapydML and RapydScript under Apache license. If you believe I should do so now, I would love to hear your argument. As of yet, I do not see a legitimate case of how the GPL license could hurt a company deciding to use RapydScript (aside from their legal department getting paranoid about the ‘GPL’ acronym). If they wish to use RapydScript as a compiler to create proprietary work, the GPL license does not affect their own code. If they wish to reuse RapydScript libraries in their proprietary code, the Apache license of the libraries will allow them to do that. If they wish to make changes to the compiler that will only be used internally by the company, the GPL license will not affect them. If they wish to release a stand-alone tool to be used with RapydScript, RapydScript’s license does not apply. If they wish to make changes to the compiler that would affect the rest of the community, then they have to release the source code for these changes.