Browse files

initial commit

  • Loading branch information...
0 parents commit b5af4875cd8af70841e83dabaafb18e7f0139a3f @rmurphey committed Apr 3, 2011
2 .gitignore
@@ -0,0 +1,2 @@
+www/js/dojo-release-1.6.0-src
+dist
23 README.md
@@ -0,0 +1,23 @@
+# Using the Dojo Toolkit for MVC JavaScript applications
+
+This is a repository based on the [Dojo
+Boilerplate]https://github.com/rmurphey/dojo-boilerplate/) for demonstrating
+simple MVC concepts using the [Dojo Toolkit](http://dojotoolkit.org).
+
+This repository is set up to work using a version of Dojo hosted on the Google
+CDN. If you'd prefer to use a local version of Dojo -- or if you're interested
+in trying out the build script at `util/build.sh` -- you'll need to run the
+setup script at `util/setup.sh` to download the full Dojo SDK. You'll also need
+to
+
+# Useful resources
+
+* [Dojo Reference Guide](http://dojotoolkit.org/reference-guide/)
+* [Introduction to Custom Dojo
+ Widgets](http://www.enterprisedojo.com/2010/09/21/introduction-to-custom-dojo-widgets/)
+
+# License
+
+The Dojo Boilerplate is licensed under the [same
+terms](http://bugs.dojotoolkit.org/browser/dojo/trunk/LICENSE) as the Dojo
+Toolkit.
25 profiles/app.js
@@ -0,0 +1,25 @@
+dependencies = {
+ stripConsole : 'all',
+ action : 'clean,release',
+ optimize : 'shrinksafe',
+ releaseName : 'js',
+ localeList : 'en-us',
+
+ layers: [
+ {
+ name: "../app/base.js",
+ resourceName : "app.base",
+ dependencies: [
+ "app.base"
+ ]
+ }
+ ],
+
+ prefixes: [
+ [ "dijit", "../dijit" ],
+ [ "dojox", "../dojox" ],
+ [ "app", "../../app" ],
+ [ "dbp", "../../dbp" ]
+ ]
+}
+
43 util/build.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+set -e
+
+DOJOVERSION="1.6.0"
+
+THISDIR=$(cd $(dirname $0) && pwd)
+SRCDIR="$THISDIR/../www"
+UTILDIR="$SRCDIR/js/dojo-release-${DOJOVERSION}-src/util/buildscripts"
+PROFILE="$THISDIR/../profiles/app.js"
+CSSDIR="$SRCDIR/css"
+DISTDIR="$THISDIR/../dist"
+
+if [ ! -d "$UTILDIR" ]; then
+ echo "Can't find Dojo build tools -- did you run ./util/setup.sh?"
+ exit 1
+fi
+
+if [ ! -f "$PROFILE" ]; then
+ echo "Invalid input profile"
+ exit 1
+fi
+
+echo "Using $PROFILE. CSS will be copied and JS will be built."
+
+# clean the old distribution files
+rm -rf "$DISTDIR"
+
+# i know this sucks, but sane-er ways didn't seem to work ... :(
+cd "$UTILDIR"
+./build.sh profileFile=../../../../../profiles/app.js releaseDir=../../../../../dist/
+cd "$THISDIR"
+
+# copy the css files
+# todo: how to do this better?
+cp -r "$CSSDIR" "$DISTDIR/css"
+
+# copy the index.html and make it production-friendly
+cp "$SRCDIR/index.html" "$DISTDIR/index.html"
+
+
+sed -i -e "s/var _dbpDev = true;//" "$DISTDIR/index.html"
+sed -i -e "s/js\/dojo-release-1.6.0-src/dist/" "$DISTDIR/index.html"
30 util/setup.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+
+VERSION="1.6.0"
+
+THISDIR=$(cd $(dirname $0) && pwd)
+OUTDIR="$THISDIR/../www/js"
+DOJODIR="dojo-release-${VERSION}-src"
+OUTDIR=$(cd "$OUTDIR" &> /dev/null && pwd || echo "")
+
+if which wget >/dev/null; then
+ GET="wget --no-check-certificate -O -"
+elif which curl >/dev/null; then
+ GET="curl -L --insecure -o -"
+else
+ echo "No cURL, no wget, no downloads :("
+ exit 1
+fi
+
+if [ "$OUTDIR" = "" ]; then
+ echo "Output directory not found"
+ exit 1
+fi
+
+if [ ! -d "$OUTDIR/$DOJODIR" ]; then
+ echo "Fetching Dojo $VERSION"
+ $GET http://download.dojotoolkit.org/release-$VERSION/$DOJODIR.tar.gz | tar -C "$OUTDIR" -xzf -
+ echo "Dojo extracted to $OUTDIR/$DOJODIR"
+fi
110 www/css/app.css
@@ -0,0 +1,110 @@
+body {
+ background-color:#222;
+ color:#f0f0f0
+}
+
+#container {
+ width:970px;
+ font-family: Helvetica, Arial, sans-serif;
+ margin: 20px auto;
+ overflow:hidden;
+}
+
+#people {
+ background-color:#ff6600;
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ padding:15px;
+ margin:0 0 40px 0;
+ overflow:hidden;
+ font-weight:bold;
+}
+
+#people li {
+ float:left;
+ margin:0 15px 0 0;
+ cursor: pointer;
+}
+
+.person .info {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+
+ display: box;
+ box-orient: horizontal;
+}
+
+ul {
+ margin: 0;
+ padding:0;
+}
+
+ul li {
+ list-style:none;
+ margin:0 0 1.2em 0;
+}
+
+p {
+ line-height: 1.2em;
+ margin:0 0 0.5em 0;
+}
+
+.latest-tweet {
+ color:#000;
+ background-color: #f5c700;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ padding:15px;
+ margin:0 0 2em 0;
+}
+
+.latest-tweet li {
+ margin:0;
+}
+
+.latest-tweet p.tweet {
+ font-size: 150%;
+}
+
+.latest-tweet p.date {
+ margin: 0;
+}
+
+p.date {
+ font-size:80%;
+}
+
+a {
+ color: #c12552;
+ text-decoration:none;
+}
+
+a:hover {
+ text-decoration:underline;
+}
+
+.twitter {
+ -webkit-box-flex: 2;
+ padding:0 50px 0 0;
+}
+
+.weather {
+ -webkit-box-flex: 1;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ padding:15px;
+ background-color: #008885;
+}
+
+h3 {
+ margin:0 0 0.5em 0;
+}
+
+.loading {
+ background-image: url(../img/loading.gif);
+ background-position: center;
+ background-repeat: no-repeat;
+}
33 www/data/people.json
@@ -0,0 +1,33 @@
+[
+ {
+ "id" : 1,
+ "name" : "Alex Sexton",
+ "twitter" : "SlexAxton",
+ "location" : "Austin, TX",
+ "img" : "img/alex.png"
+ },
+
+ {
+ "id" : 2,
+ "name" : "Paul Irish",
+ "twitter" : "paul_irish",
+ "location" : "San Francisco, CA",
+ "img" : "img/paul.png"
+ },
+
+ {
+ "id" : 3,
+ "name" : "Adam Sontag",
+ "twitter" : "ajpiano",
+ "location" : "New York, NY",
+ "img" : "img/adam.png"
+ },
+
+ {
+ "id" : 4,
+ "name" : "Rebecca Murphey",
+ "twitter" : "rmurphey",
+ "location" : "Durham, NC",
+ "img" : "img/rebecca.png"
+ }
+]
BIN www/img/adam.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN www/img/alex.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN www/img/loading.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN www/img/paul.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN www/img/rebecca.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 www/index.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title></title>
+ <link rel="stylesheet" href="css/app.css">
+</head>
+<body>
+
+ <div id="container">
+ <div id="people"></div>
+ <div id="detail"></div>
+ </div>
+
+ <!--
+
+ uncomment this block to use a local dojo.js; note that you must
+ run the setup script at util/setup.sh in order for this to work.
+ note also that you'll need to remove the script tags that are currently
+ being used
+
+ <script>
+ var _dbpDev = true;
+ djConfig = _dbpDev ? {
+ modulePaths : {
+ app : "../../app"
+ }
+ } : {};
+ </script>
+ <script src="js/dojo-release-1.6.0-src/dojo/dojo.js"></script>
+
+ -->
+
+ <script>
+ var _dbpDev = true;
+ djConfig = _dbpDev ? {
+ baseUrl : './',
+ modulePaths : {
+ app : "js/app",
+ dbp : "js/dbp"
+ }
+ } : {};
+ </script>
+ <script src="https://ajax.googleapis.com/ajax/libs/dojo/1.6.0/dojo/dojo.xd.js"></script>
+ <script>
+ dojo.ready(function() {
+ dojo.require("app.base");
+ });
+ </script>
+</body>
+</html>
91 www/js/app/base.js
@@ -0,0 +1,91 @@
+dojo.provide('app.base');
+/**
+ * This file is your application's base JavaScript file;
+ * it is loaded into the page by the dojo.require() call in
+ * index.html. You can write code in this file, use it to
+ * express dependencies on other files, or both. Generally,
+ * this file should be used only for bootstrapping code;
+ * actual functionality should be placed in other files inside
+ * the www/js/app directory.
+ */
+
+/**
+ * You can specify dependencies on other files by adding
+ * dojo.require() statements for them:
+ *
+ * dojo.require('dijit.Dialog');
+ *
+ * This works for your application's files, too:
+ *
+ * dojo.require('app.Foo');
+ *
+ * The above would look for a file located at
+ * www/js/app/Foo.js; however, it's important to note
+ * that this only works because we've specified a modulePath for
+ * the 'app' namespace in index.html. If we do not specify a
+ * modulePath for a namespace, dojo.require will assume that the
+ * namespace corresponds to a directory that is a sibling of
+ * the directory that contains dojo.js. The modulePath setting
+ * in index.html overrides that default, providing a location
+ * for the namespace relative to the location of dojo.js.
+ *
+ * Note also that any files you include via dojo.require()
+ * MUST include a call to dojo.provide at the beginning;
+ * the dojo.provide() function should be passed a string
+ * that specifies how you expect the module to be referred
+ * to in dojo.require() calls:
+ *
+ * dojo.provide('app.Foo');
+ *
+ * Finally, note that you do not need to express all of your
+ * application's dependencies in this one file; individual files
+ * can express their own dependencies as well.
+ */
+dojo.require('app.views.Person');
+dojo.require('app.views.People');
+dojo.require('app.models.People');
+
+/**
+ * Any functionality that depends on the DOM being available
+ * should be passed inside a function to dojo.ready. If you're
+ * making a single-page app, this is your application controller.
+ */
+dojo.ready(function() {
+ var peopleData = app.models.People;
+
+ // load the initial data for the page
+ dojo.when(peopleData.load(), function() {
+
+ var people = peopleData.getPeople(),
+ peopleWidget = new app.views.People({ people : people }, 'people'),
+ personWidget,
+ person;
+
+ dojo.connect(peopleWidget, 'onSelect', function(personId) {
+
+ // destroy the old person widget if there is one
+ if (personWidget) { personWidget.destroy(); }
+
+ // get an instance of the Person model for the requested person
+ person = peopleData.getPerson(personId);
+
+ // set up the person widget, passing in the person model
+ personWidget = new app.views.Person({
+ person : person
+ }).placeAt('detail');
+
+ // ask the model for the person's tweets, and pass them
+ // to the widget once we have them
+ person.getTweets().then(function(tweets) {
+ personWidget.set('tweets', tweets);
+ });
+
+ // ask the model for the person's weather, and pass it
+ // to the widget once we have it
+ person.getWeather().then(function(weather) {
+ personWidget.set('weatherData', weather);
+ });
+
+ });
+ });
+});
34 www/js/app/models/People.js
@@ -0,0 +1,34 @@
+dojo.provide('app.models.People');
+
+dojo.require('dojo.store.Memory');
+dojo.require('app.models.Person');
+
+(function() {
+
+var store;
+
+app.models.People = {
+ load : function() {
+ if (store) { return store.data; }
+
+ return dojo.xhrGet({
+ url : 'data/people.json',
+ handleAs : 'json',
+ load : function(data) {
+ store = new dojo.store.Memory({ data : data });
+ }
+ });
+ },
+
+ getPerson : function(id) {
+ return new app.models.Person(store.get(id));
+ },
+
+ getPeople : function() {
+ return dojo.map(store.data, function(p) {
+ return new app.models.Person(p);
+ });
+ }
+};
+
+}());
22 www/js/app/models/Person.js
@@ -0,0 +1,22 @@
+dojo.provide('app.models.Person');
+
+dojo.require('app.services.Twitter');
+dojo.require('app.services.YQL');
+
+(function() {
+
+dojo.declare('app.models.Person', [], {
+ constructor : function(data) {
+ this.data = data;
+ },
+
+ getTweets : function() {
+ return app.services.Twitter.tweets(this.data.twitter);
+ },
+
+ getWeather : function() {
+ return app.services.YQL.weather(this.data.location);
+ }
+});
+
+}());
33 www/js/app/services/Twitter.js
@@ -0,0 +1,33 @@
+dojo.provide('app.services.Twitter');
+
+dojo.require('dojo.io.script');
+dojo.require('dojo.string');
+
+app.services.Twitter = {
+ tweets : function(username) {
+ console.log('username is', username);
+ var url = 'http://twitter.com/status/user_timeline/' + username + '.json',
+ dfd = new dojo.Deferred();
+
+ dojo.io.script.get({
+ url : url,
+ callbackParamName : 'callback',
+ content : { count : 10, format : 'json' },
+ load : dojo.hitch(this, function(data) {
+ dfd.resolve(dojo.map(data, function(t) {
+ return this._processTweet(t, username);
+ }, this));
+ })
+ });
+
+ return dfd.promise;
+ },
+
+ _processTweet : function(t, username) {
+ return {
+ text : t.text,
+ date : t.created_at,
+ url : [ 'http://twitter.com', username, 'status', t.id_str ].join('/')
+ };
+ }
+};
50 www/js/app/services/YQL.js
@@ -0,0 +1,50 @@
+dojo.provide('app.services.YQL');
+
+dojo.require('dojo.io.script');
+
+app.services.YQL = {
+ _doQuery : function(config) {
+ dojo.io.script.get({
+ url : 'http://query.yahooapis.com/v1/public/yql',
+ callbackParamName : 'callback',
+ content : dojo.mixin({
+ format : 'json'
+ }, config.content),
+ load : config.load
+ });
+ },
+
+ weather : function(location) {
+ var dfd = new dojo.Deferred();
+
+ dojo.when(this.zip(location), dojo.hitch(this, function(zip) {
+ this._doQuery({
+ content : {
+ q : 'select * from weather.forecast where location=' + zip
+ },
+ load : function(data) {
+ console.log('loaded weather', data);
+ dfd.resolve(data.query.results.channel.item);
+ }
+ });
+ }));
+
+ return dfd.promise;
+ },
+
+ zip : function(location) {
+ var dfd = new dojo.Deferred();
+
+ this._doQuery({
+ content : {
+ q : 'select * from geo.placefinder where text="' + location + '"'
+ },
+ load : function(data) {
+ console.log('loaded location data', data);
+ dfd.resolve(data.query.results.Result.uzip);
+ }
+ });
+
+ return dfd.promise;
+ }
+};
35 www/js/app/views/People.js
@@ -0,0 +1,35 @@
+dojo.provide('app.views.People');
+
+dojo.declare('app.views.People', [ dijit._Widget, dijit._Templated ], {
+ templateString : dojo.cache('app.views', 'People/People.html'),
+ personTemplate : dojo.cache('app.views', 'People/Person.html'),
+
+ postMixInProperties : function() {
+ this.listHtml = dojo.map(this.people, function(p) {
+ return dojo.string.substitute(this.personTemplate, p.data);
+ }, this).join('');
+ },
+
+ postCreate : function() {
+ this.connect(this.domNode, 'click', '_handleClick');
+ },
+
+ _handleClick : function(e) {
+ var t = e.target,
+ id;
+
+ if (t.nodeName.toLowerCase() !== 'li') { return; }
+
+ dojo.forEach(this.domNode.children, function(c) {
+ dojo.removeClass(c, 'selected');
+ });
+
+ dojo.addClass(t, 'selected');
+
+ this.onSelect(dojo.attr(t, 'data-id'));
+ },
+
+ onSelect : function(id) {
+ // stub for connection
+ }
+});
3 www/js/app/views/People/People.html
@@ -0,0 +1,3 @@
+<ul class="people">
+ ${listHtml}
+</ul>
1 www/js/app/views/People/Person.html
@@ -0,0 +1 @@
+<li data-id=${id}>${name}</li>
39 www/js/app/views/Person.js
@@ -0,0 +1,39 @@
+dojo.provide('app.views.Person');
+
+dojo.require('dijit._Widget');
+dojo.require('dijit._Templated');
+
+dojo.declare('app.views.Person', [ dijit._Widget, dijit._Templated ], {
+ templateString : dojo.cache('app.views', 'Person/Person.html'),
+ tweetTemplate : dojo.cache('app.views', 'Person/Tweet.html'),
+ weatherTemplate : dojo.cache('app.views', 'Person/Weather.html'),
+
+ // mark the weather and twitter areas as loading
+ postCreate : function() {
+ dojo.forEach([ 'latestTweet', 'olderTweets', 'weather' ], function(n) {
+ dojo.addClass(this[n], 'loading');
+ }, this);
+ },
+
+ _setTweetsAttr : function(tweets) {
+ dojo.removeClass(this.olderTweets, 'loading');
+ dojo.removeClass(this.latestTweet, 'loading');
+
+ var latest = tweets.shift();
+
+ this.olderTweets.innerHTML = dojo.map(tweets, function(t) {
+ return dojo.string.substitute(this.tweetTemplate, t);
+ }, this).join('');
+
+ this.latestTweet.innerHTML = dojo.string.substitute(
+ this.tweetTemplate, latest
+ );
+ },
+
+ _setWeatherDataAttr : function(weather) {
+ dojo.removeClass(this.weather, 'loading');
+ this.weather.innerHTML = dojo.string.substitute(
+ this.weatherTemplate, weather
+ );
+ }
+});
9 www/js/app/views/Person/Person.html
@@ -0,0 +1,9 @@
+<div class="person">
+ <h1><img src="${person.data.img}" alt="${person.data.name}">
+ ${person.data.name}</h2>
+ <ul class="latest-tweet" dojoAttachPoint="latestTweet"></ul>
+ <div class="info">
+ <ul class="twitter" dojoAttachPoint="olderTweets"></ul>
+ <div class="weather" dojoAttachPoint="weather"></div>
+ </div>
+</div>
4 www/js/app/views/Person/Tweet.html
@@ -0,0 +1,4 @@
+<li>
+ <p class="tweet">${text}</p>
+ <p class="date"><a href="${url}">${date}</a></p>
+</li>
4 www/js/app/views/Person/Weather.html
@@ -0,0 +1,4 @@
+<div>
+ <h3>${title}</h3>
+ <p>${description}</p>
+</div>
13 www/js/app/views/Weather.js
@@ -0,0 +1,13 @@
+dojo.provide('app.views.Weather');
+
+dojo.require('dijit._Widget');
+dojo.require('dijit._Templated');
+dojo.require('app.services.YQL');
+
+dojo.declare('app.views.Weather', [ dijit._Widget, dijit._Templated ], {
+ templateString : dojo.cache('app.views', 'Weather/Weather.html'),
+
+ postCreate : function() {
+ dojo.when(app.services.YQL.weather(this.location),
+ }
+});

0 comments on commit b5af487

Please sign in to comment.