diff --git a/README.md b/README.md index af5a884..76a6c87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,44 @@ -#Angularjs in Meteor -##How to use it -The angularjs app is always called meteorapp. +# Angularjs in Meteor + +## Configuration + +The angular HTML page will be served by meteor. The page generation can be configured in the `server.js` file. The `angularAppConfig` object is used by default: + + var angularAppConfig = { + name: 'meteorapp', + appController: 'AppCtrl', + template: { + placeholderElement: '', + name: 'angular.html', + locations: ['bundle/static/', 'public/'], + } + +* `name` - name of the Angular app (used for `ng-app` attribute and name of app module, default: `'meteorapp'`) +* appController - name used to define the main controller on the body element (default: `'AppCtrl'`) +* template - config options for how to load and insert the Angular template + +### Angular template config options + +* `placeholderElement` - the tag element to substitute with the template (default: `''`') +* `name` - the name of the template file (default: `'angular.html'`) +* `locations` - relative folder locations to search for the Angular template. + +Note that `locations` are set up to first try in the expected "deploy" location, then the development location. + +If you define a config file `angularAppConfig.json`, it will be used for angular app configuration. Here an example of a custom config file: + + { + name: 'my-meteorapp', + appController: 'AppController', + template: { + placeholderElement: '
', + name: 'main-view.html', + locations: ['bundle/static/', 'public/'], + } + +## Configuring the Angular app + +Define the main angular module, named to match the config Angular app name config setting for the meteor server (default: `'meteorapp'`): angular.module('meteorapp', []). config(['$routeProvider', function($routeProvider) { @@ -8,13 +46,55 @@ The angularjs app is always called meteorapp. when('/index', {templateUrl: 'partials/index.html', controller: MeteorCtrl}). otherwise({redirectTo: '/'}); }]); -###Directory structure + +For a complete example, see [meteor-angular-leaderboard](https://github.com/bevanhunt/meteor-angular-leaderboard) + + # coffeescript example + + angular.module('meteorapp', []) + .config ['$routeProvider', '$locationProvider', ($routeProvider, $locationProvider) -> + $locationProvider.html5Mode(true) + $routeProvider.when '/' + controller: 'home' +] + + +### Directory structure + +Create the angular template file with a name and location matching your server config settings. + +Default structure: /public /partials angular.html(Main screen should contain body content) -###Usage +Custom structure: + + /public + /partials + /angular + main-view.html(Main screen should contain body content) + +The router should load the index file when you navigate to root `'/'` of your app. The index file will then be served by meteor (see server.js) + +Example 'index.html' file + + /client + index.html + +Example index HTML file + + + Leaderboard + + +Meteor server will magically create the `` and `` element. + +###Usage of $meteor + +You can simply inject the $meteor module provided anywhere you want to use it, typically in your meteor-enabled controllers. Then execute the provided Meteor commands (see client.js) + app.controller('MeteorCtrl', ['$scope','$meteor',function($scope,$meteor){ $scope.todos = $meteor("todos").find({}); $meteor("todos").insert({ @@ -28,6 +108,49 @@ The angularjs app is always called meteorapp.
-###Deploying -Make sure that you always write angularjs code that can be minified, else use the --debug function. To deploy with Heroku use this buildpack. Thanks to @mimah + +### Current user + +In order to add Meteor current user functionality: + +See [current user issue #18](https://github.com/lvbreda/Meteor_angularjs/issues/18) + +You can use something like: + + app.directive('currentUser', function($timeout) { + return function(scope, element) { + var timeoutId; // timeoutId, so that we can cancel the user updates + // used to update the UI + function updateUser() { + element.text((Meteor.user() != null)? Meteor.user()._id : "anonymous"); + } + // schedule update in one tenth of a second + function updateLater() { + // save the timeoutId for canceling + timeoutId = $timeout(function() { + updateUser(); // update DOM + updateLater(); // schedule another update + }, 100); + } + // listen on DOM destroy (removal) event, and cancel the next UI update + // to prevent updating user after the DOM element was removed. + element.bind('$destroy', function() { + $timeout.cancel(timeoutId); + }); + updateLater(); // kick off the UI update process. + } + }); + +The `currentUser` directive is by default a HTML element attribute. + + + +Then in a controller: + + $scope.currentUser = $meteor('users').find({_id:Meteor.userId()}) + +### Deploying + +Make sure that you always write angularjs code that can be minified, else use the `--debug` function. To deploy with Heroku use this buildpack. Thanks to @mimah + https://github.com/mimah/heroku-buildpack-meteorite diff --git a/client.js b/client.js index 8c5acb7..fa4b899 100644 --- a/client.js +++ b/client.js @@ -3,6 +3,10 @@ function() { var self = this; self.collections = {}; self.getCollection = function(name) { + if(name == 'users'){ + return Meteor.users; + } + if (self.collections[name]) { return self.collections[name]; } else { diff --git a/server.js b/server.js index 76eaaba..3191cb2 100644 --- a/server.js +++ b/server.js @@ -3,44 +3,117 @@ var fs = Npm.require("fs"); var path = Npm.require("path"); var Fiber = Npm.require("fibers"); + +// default config +var angularAppConfig = { + name: 'meteorapp', + appController: 'AppCtrl', + template: { + placeholderElement: "", + name: 'angular.html', + locations: ['bundle/static/', 'public/'], + notice: "This is used as your main page, this should contain the contents of the body.", + } +} + +angularAppConfig.template.resolvedPaths = function() { + var self = this; + var resolved = []; + for (var path in this.paths) { + paths.push(path + self.name); + } + return resolved; +} + +var appConfig = { + readFile: function(filePath) { + new String(fs.readFileSync(path.resolve(filePath))); + }, + getAppHtml: function() { + try { + return this.readFile('bundle/app.html'); + } catch(e) { + return this.readFile('.meteor/local/build/app.html'); + } + }, + + getAngularTemplate: function() { + var templatePaths = angularApp.template.locations; + + // iterate all templatePaths and try each + for (var templatePath in templatePaths) + // only attempt read if file exists + if (fs.existsSync(templatePath) + return this.readFile(templatePath); + } + console.log("Angularjs\n______\nCreate any of: " + templatePaths.join(', ') + "\n " + angularApp.template.notice); + }, + replaceCode: function() { + var element = function(tag) { + return "<" + tag + " "; + } + var ngAttr = function(name, value) { + return " ng-" + name + "='" + value + "' "; + } + + var loadAngularAppConfig = function() { + if (fs.existsSync('angularAppConfig.json')) { + try { + angularApp = JSON.parse(this.readFile('angularAppConfig.json')); + } catch (e) { + console.log("Angularjs\n______\nCreate a file: 'angularAppConfig.json' to override the default settings" + return null; + } + } + } + + var angularApp = loadAngularAppConfig || angularAppConfig; + + angularApp.template.resolvedPaths = angularAppConfig.template.resolvedPaths; + + // + code = appConfig.getAppHtml(); + + // insert Angular template + code = code.replace(angularApp.template.placeholderElement, appConfig.getAngularTemplate()); + + var htmlElem = element('html'); + var htmlReplacement = htmlElem + ngAttr('app', angularApp.name); + + // insert ng-app on html element + code = code.replace(htmlElem, htmlReplacement); + + // TODO: Allow insert on html element? + // insert ng-controller="AppCtrl" on angular placeholder element + if (angularApp.appController) { + var plh = element(angularApp.template.placeholderElement); + var plhReplacement = plh + ngAttr('controller', angularApp.appController); + + code = code.replace(plh, plhReplacement); + } + + if (typeof __meteor_runtime_config__ !== 'undefined') { + code = code.replace("// ##RUNTIME_CONFIG##", this.runtimeConfig()); + } + } + + runtimeConfig: function() { + "__meteor_runtime_config__ = " + JSON.stringify(__meteor_runtime_config__) + ";"); + } +} + + __meteor_bootstrap__.app .use(connect.query()) .use(function (req, res, next) { // Need to create a Fiber since we're using synchronous http calls Fiber(function() { - try{ - var code = fs.readFileSync(path.resolve('bundle/app.html')); - }catch(e){ - var code = fs.readFileSync(path.resolve('.meteor/local/build/app.html')); - } - var angular = ""; - try{ - angular = fs.readFileSync(path.resolve('bundle/static/angular.html')); - }catch(e){ - if(fs.existsSync("public/angular.html")){ - angular = fs.readFileSync(path.resolve('public/angular.html')); - }else{ - console.log("Angularjs\n______\nCreate public/angular.html\n This is used as your main page, this should contain the contents of the body."); - } - } - - - code = new String(code); - // console.log((new String(angular)).join()); - code = code.replace("",new String(angular)); - code = code.replace("",''); - if (typeof __meteor_runtime_config__ !== 'undefined') { - code = code.replace( - "// ##RUNTIME_CONFIG##", - "__meteor_runtime_config__ = " + - JSON.stringify(__meteor_runtime_config__) + ";"); - } - + var code = appConfig.replaceCode(); + res.writeHead(200, {'Content-Type': 'text/html'}); - res.write(code); - res.end(); - return; - //next(); + res.write(code); + res.end(); + return; }).run(); });