From ce1a362952514d191e9654d8a4ce1917cf72dfe5 Mon Sep 17 00:00:00 2001 From: Peter Grman Date: Thu, 7 May 2015 17:42:56 +0200 Subject: [PATCH] Add type definitions for angular-meteor --- angular-meteor/angular-meteor-tests.ts | 253 +++++++++++++++++++ angular-meteor/angular-meteor.d.ts | 322 +++++++++++++++++++++++++ 2 files changed, 575 insertions(+) create mode 100644 angular-meteor/angular-meteor-tests.ts create mode 100644 angular-meteor/angular-meteor.d.ts diff --git a/angular-meteor/angular-meteor-tests.ts b/angular-meteor/angular-meteor-tests.ts new file mode 100644 index 000000000..be75f196b --- /dev/null +++ b/angular-meteor/angular-meteor-tests.ts @@ -0,0 +1,253 @@ +/// + +interface ITodo { + _id?: string; + name: string; + public?: boolean; + sticky?: boolean; +} + +interface TodoAngularMeteorObject extends ITodo, angular.meteor.AngularMeteorObject {} + +interface CustomScope extends angular.meteor.IScope { + sticky: boolean; + + todos: angular.meteor.AngularMeteorCollection; + stickyTodos: angular.meteor.AngularMeteorCollection; + notAutoTodos: angular.meteor.AngularMeteorCollection; + + todo: ITodo; + todoNotAuto: TodoAngularMeteorObject; + + save: (todo: ITodo) => void; + saveAll: () =>void; + autoSave: (todo: ITodo) => void; + remove: (todoId: string) => void; + removeAll: () => void; + removeAuto: (todo: ITodo) => void; + toSticky: (todo: ITodo) => void; +} + +var Todos = new Mongo.Collection('todos'); + +var app = angular.module('angularMeteorTestApp'); + +app.controller("mainCtrl", ['$scope', '$meteor', ($scope: CustomScope, $meteor: angular.meteor.IMeteorService) => { + // Bind all the todos to $scope.todos + $scope.todos = $meteor.collection(Todos); + + $scope.sticky = true; + // Bind all sticky todos to $scope.stickyTodos + // Binds the query to $scope.sticky so that if it changes, Meteor will re-run the query and bind it + // to $scope.stickyTodos + $scope.stickyTodos = $meteor.collection(function(){ + return Todos.find({sticky: $scope.getReactively('sticky')}); + }); + + // Bind without auto-save all todos to $scope.notAutoTodos + $scope.notAutoTodos = $meteor.collection(Todos, false).subscribe("publicTodos"); + + $scope.todoNotAuto = $meteor.object(Todos, 'TodoID', false); + $scope.todo = $scope.todoNotAuto.getRawObject(); + $scope.todoNotAuto.reset(); + $scope.todoNotAuto.save($scope.todo).then((data) => { return data == 1; });; + + // todo might be an object like this {text: "Learn Angular", sticky: false} + // or an array like this: + // [{text: "Learn Angular", sticky: false}, {text: "Hello World", sticky: true}] + + $scope.save = function(todo) { + $scope.notAutoTodos.save(todo); + }; + + $scope.saveAll = function() { + $scope.notAutoTodos.save(); + }; + + $scope.autoSave = function(todo) { + $scope.todos.push(todo); + }; + + // todoId might be an string like this "WhrnEez5yBRgo4yEm" + // or an array like this ["WhrnEez5yBRgo4yEm","gH6Fa4DXA3XxQjXNS"] + $scope.remove = function(todoId) { + $scope.notAutoTodos.remove(todoId); + }; + + $scope.removeAll = function() { + $scope.notAutoTodos.remove(); + }; + + $scope.removeAuto = function(todo) { + $scope.todos.splice( $scope.todos.indexOf(todo), 1 ); + } + + $scope.toSticky = function(todo) { + if (angular.isArray(todo)){ + angular.forEach(todo, function(object) { + object.sticky = true; + }); + } else { + todo.sticky = true; + } + + $scope.stickyTodos.save(todo); + }; + + var todoObject = {name:'first todo'}; + var todosArray = [{name:'second todo'}, {name:'third todo'}]; + var todoSecondObject = {name:'forth todo'}; + + $scope.todos.save(todoObject); // todos equals [{name:'first todo'}] + + $scope.todos.save(todosArray); // todos equals [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}] + + $scope.todos.push(todoSecondObject); // The scope variable equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}, {name:'forth todo'}] + // but the collection still equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}] + + $scope.todos.save(); // Now the collection also equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}, {name:'forth todo'}] + + $scope.todos.remove('firstTodoId'); // scope and collection equals to [{name:'second todo'}, {name:'third todo'}, {name:'forth todo'}] + + var todoIdsArray = ['secondTodoId', 'thirdTodoId']; + $scope.todos.remove(todoIdsArray); // removes everything matches the array of IDs both in scope and in collection + + $scope.todos.pop(); // removes only in scope + + $scope.todos.remove(); // syncs also in Meteor collection + + // Subscribe -> + + $meteor.subscribe('todos').then((subscriptionHandle) => { + // Bind all the todos to $scope.todos + $scope.todos = $meteor.collection(Todos); + + console.log($scope.todos + ' is ready'); + + // You can use the subscription handle to stop the subscription if you want + subscriptionHandle.stop(); + }); + + $scope.subscribe('todos').then((subscriptionHandle) => { + // Bind all the todos to $scope.books + $scope.todos = $meteor.collection(Todos); + + console.log($scope.todos + ' is ready'); + + // No need to stop the subscription, it will automatically close on scope destroy + }); + + $meteor.call('subscribe', $scope.todo._id, $scope.currentUser._id).then((data) => { + // Handle success + console.log('success subscribing', data.name); + }, (err) => { + // Handle error + console.log('failed', err); + }); + + if (!$scope.loggingIn) { + $meteor.waitForUser(); + + $meteor.requireUser(); + + $meteor.requireValidUser(user => { + return user.username == 'admin'; + }); + + $meteor.loginWithPassword('user', 'password').then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + + $meteor.createUser({ + username:'moma', + email:'example@gmail.com', + password: 'Bksd@asdf', + profile: {expertize: 'Developer'} + }).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + + $meteor.changePassword('old', 'new232f3').then(() => { + console.log('Change password success'); + }, err => { + console.log('Error changing password - ', err); + }); + + $meteor.forgotPassword({email: 'sample@gmail.com'}).then(() => { + console.log('Success sending forgot password email'); + }, err => { + console.log('Error sending forgot password email - ', err); + }); + + $meteor.resetPassword('tokenID', 'new232f3').then(() => { + console.log('Reset password success'); + }, err => { + console.log('Error resetting password - ', err); + }); + + $meteor.verifyEmail('tokenID').then(() => { + console.log('Success verifying password '); + }, err => { + console.log('Error verifying password - ', err); + }); + + $meteor.logout().then(() => { + console.log('Logout success'); + }, err => { + console.log('logout error - ', err); + }); + + $meteor.logoutOtherClients().then(() => { + console.log('Logout success'); + }, err => { + console.log('logout error - ', err); + }); + + var loginWithOptions = {requestPermissions: ['email']}; + + $meteor.loginWithFacebook({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + $meteor.loginWithGithub({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + $meteor.loginWithGoogle({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + $meteor.loginWithMeetup({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + $meteor.loginWithTwitter({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + $meteor.loginWithWeibo({requestPermissions: ['email']}).then(() => { + console.log('Login success'); + }, err => { + console.log('Login error - ', err); + }); + } + + $meteor.autorun($scope, () => { console.log("Aurorun triggered."); }); + $meteor.getCollectionByName('collectionName'); + + // requires meteor add mdg:camera + $meteor.getPicture().then(function(data){ + $scope['picture'] = data; + }); + + $meteor.session('counter').bind($scope, 'counter'); +}]); diff --git a/angular-meteor/angular-meteor.d.ts b/angular-meteor/angular-meteor.d.ts new file mode 100644 index 000000000..18f2e033e --- /dev/null +++ b/angular-meteor/angular-meteor.d.ts @@ -0,0 +1,322 @@ +// Type definitions for Angular JS Meteor v0.8.8 (angular.meteor module) +// Project: https://github.com/Urigo/angular-meteor +// Definitions by: Peter Grman +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// +/// + +declare module angular.meteor { + interface IRootScopeService extends angular.IRootScopeService { + /** + * The current logged in user and it's data. it is null if the user is not logged in. A reactive data source. + */ + currentUser: Meteor.User; + + /** + * True if a login method (such as Meteor.loginWithPassword, Meteor.loginWithFacebook, or Accounts.createUser) is currently in progress. + * A reactive data source. Can be use to display animation while user is logging in. + */ + loggingIn: boolean; + } + + interface IScope extends angular.IScope, IRootScopeService { + /** + * A method to get a $scope variable and watch it reactivly + * + * @param scopeVariableName - The name of the scope's variable to bind to + * @param [objectEquality=false] - Watch the object equality using angular.equals instead of comparing for reference equality, deeper watch but also slower + */ + getReactively(scopeVariableName: string, objectEquality?: boolean): ReactiveResult; + + /** + * A service which is a wrapper for Meteor.subscribe. It subscribes to a Meteor.publish method in the client and returns a AngularJS promise when ready. + * Calling $scope.subscribe will automatically stop the subscription when the scope is destroyed. + * + * @param name - Name of the subscription. Matches the name of the server's publish() call. + * @param publisherArguments - Optional arguments passed to publisher function on server. + * + * @return The promise solved successfully when subscription is ready. The success promise holds the subscription handle. + */ + subscribe(name: string, ...publisherArguments: any[]): angular.IPromise; + } + + /** + * $meteor in angularjs + */ + interface IMeteorService { + /** + * A service that wraps the Meteor collections to enable reactivity within AngularJS. + * + * @param collection - A Meteor Collection or a reactive function to bind to. + * - Reactive function can be used with $scope.getReactively to add $scope variable as reactive variable to the cursor. + * @param [autoClientSave=true] - By default, changes in the Angular collection will automatically update the Meteor collection. + * - However if set to false, changes in the client won't be automatically propagated back to the Meteor collection. + */ + collection(collection: Mongo.Collection|ReactiveResult, autoClientSave?: boolean): AngularMeteorCollection; + + /** + * A service that wraps the Meteor collections to enable reactivity within AngularJS. + * + * @param collection - A Meteor Collection or a reactive function to bind to. + * - Reactive function can be used with $scope.getReactively to add $scope variable as reactive variable to the cursor. + * @param [autoClientSave=true] - By default, changes in the Angular collection will automatically update the Meteor collection. + * - However if set to false, changes in the client won't be automatically propagated back to the Meteor collection. + * @param [updateCollection] - A collection object which will be used for updates (insert, update, delete). + */ + collection(collection: Mongo.Collection|ReactiveResult, autoClientSave: boolean, updateCollection: Mongo.Collection): AngularMeteorCollection2; + + /** + * A service that wraps a Meteor object to enable reactivity within AngularJS. + * Finds the first document that matches the selector, as ordered by sort and skip options. Wraps collection.findOne + * + * @param collection - A Meteor Collection to bind to. + * @param selector - A query describing the documents to find or just the ID of the document. + * - $meteor.object will find the first document that matches the selector, + * - as ordered by sort and skip options, exactly like Meteor's collection.findOne + * @param [autoClientSave=true] - By default, changes in the Angular object will automatically update the Meteor object. + * - However if set to false, changes in the client won't be automatically propagated back to the Meteor object. + */ + object(collection: Mongo.Collection, selector: Mongo.Selector|Mongo.ObjectID|string, autoClientSave?: boolean): AngularMeteorObject; + + /** + * A service which is a wrapper for Meteor.subscribe. It subscribes to a Meteor.publish method in the client and returns a AngularJS promise when ready. + * + * @param name - Name of the subscription. Matches the name of the server's publish() call. + * @param publisherArguments - Optional arguments passed to publisher function on server. + * + * @return The promise solved successfully when subscription is ready. The success promise holds the subscription handle. + */ + subscribe(name: string, ...publisherArguments: any[]): angular.IPromise; + + /** + * A service service which wraps up Meteor.methods with AngularJS promises. + * + * @param name - Name of method to invoke + * @param methodArguments - Optional method arguments + * + * @return The promise solves successfully with the return value of the method or return reject with the error from the method. + */ + call(name: string, ...methodArguments: any[]): angular.IPromise; + + // User Authentication BEGIN -> + + /** + * Returns a promise fulfilled with the currentUser when the user subscription is ready. + * This is useful when you want to grab the current user before the route is rendered. + * If there is no logged in user, it will return null. + * See the “Authentication with Routers” section of our tutorial for more information and a full example. + */ + waitForUser(): angular.IPromise; + + /** + * Resolves the promise successfully if a user is authenticated and rejects otherwise. + * This is useful in cases where you want to require a route to have an authenticated user. + * You can catch the rejected promise and redirect the unauthenticated user to a different page, such as the login page. + * See the “Authentication with Routers” section of our tutorial for more information and a full example. + */ + requireUser(): angular.IPromise; + + /** + * Resolves the promise successfully if a user is authenticated and the validatorFn returns true; rejects otherwise. + * This is useful in cases where you want to require a route to have an authenticated user and do extra validation like the user's role or group. + * You can catch the rejected promise and redirect the unauthenticated user to a different page, such as the login page. + * See the “Authentication with Routers” section of our tutorial for more information and a full example. + * + * The mandatory validator function will be called with the authenticated user as the single param and it's expected to return true in order to resolve. + * If it returns a string, the promise will be rejected using said string as the reason. + * Any other return (false, null, undefined) will be rejected with the default "FORBIDDEN" reason. + */ + requireValidUser(validatorFn: (user: Meteor.User) => boolean|string): angular.IPromise; + + /** + * Log the user in with a password. + * + * @param user - Either a string interpreted as a username or an email; or an object with a single key: email, username or id. + * @param password - The user's password. + */ + loginWithPassword(user: string|{email: string}|{username: string}|{id: string}, password: string): angular.IPromise; + + /** + * Create a new user. More information: http://docs.meteor.com/#/full/accounts_createuser + * + * @param options.username - A unique name for this user. Either this, or email is required. + * @param options.email - The user's email address. Either this, or username is required. + * @param options.password - The user's password. This is not sent in plain text over the wire. + * @param options.profile - The user's profile, typically including the name field. + */ + createUser(options: {username?: string; email?: string; password: string; profile?: Object}): angular.IPromise; + + /** + * Change the current user's password. Must be logged in. + * + * @param oldPassword - The user's current password. This is not sent in plain text over the wire. + * @param newPassword - A new password for the user. This is not sent in plain text over the wire. + */ + changePassword(oldPassword: string, newPassword: string): angular.IPromise; + + /** + * Request a forgot password email. + * + * @param options.email - The email address to send a password reset link. + */ + forgotPassword(options: {email: string}): angular.IPromise; + + /** + * Reset the password for a user using a token received in email. Logs the user in afterwards. + * + * @param token - The token retrieved from the reset password URL. + * @param newPassword - A new password for the user. This is not sent in plain text over the wire. + */ + resetPassword(token: string, newPassword: string): angular.IPromise; + + /** + * Marks the user's email address as verified. Logs the user in afterwards. + * + * @param token - The token retrieved from the reset password URL. + */ + verifyEmail(token: string): angular.IPromise; + + loginWithFacebook: ILoginWithExternalService; + loginWithTwitter: ILoginWithExternalService; + loginWithGoogle: ILoginWithExternalService; + loginWithGithub: ILoginWithExternalService; + loginWithMeetup: ILoginWithExternalService; + loginWithWeibo: ILoginWithExternalService; + + /** + * Log the user out. + * + * @return Resolves with no arguments on success, or reject with a Error argument on failure. + */ + logout(): angular.IPromise; + + /** + * Log out other clients logged in as the current user, but does not log out the client that calls this function. + * For example, when called in a user's browser, connections in that browser remain logged in, + * but any other browsers or DDP clients logged in as that user will be logged out. + * + * @return Resolves with no arguments on success, or reject with a Error argument on failure. + */ + logoutOtherClients(): angular.IPromise; + + // <- User Authentication END + // $meteorUtils BEGIN -> + + /** + * @param scope - The AngularJS scope you use the autorun on. + * @param fn - The function that will re-run every time a reactive variable changes inside it. + */ + autorun(scope: angular.IScope, fn: Function): void; + + /** + * @param collectionName - The name of the collection you want to get back + */ + getCollectionByName(collectionName: string): Mongo.Collection; + + // <- $meteorUtils END + // $meteorCamera BEGIN -> + + /** + * A helper service for taking pictures across platforms. + * Must add mdg:camera package to use! (meteor add mdg:camera) + * + * @param [options] - options is an optional argument that is an Object with the following possible keys: + * @param options.width - An integer that specifies the minimum width of the returned photo. + * @param options.height - An integer that specifies the minimum height of the returned photo. + * @param options.quality - A number from 0 to 100 specifying the desired quality of JPEG encoding. + * + * @return The promise solved successfully when the picture is taken with the data as a parameter or rejected with an error as a parameter in case of error. + */ + getPicture(options?: {width?: number; height?: number; quality?: number}): angular.IPromise; + + // <- $meteorCamera END + + /** + * A service that binds a scope variable to a Meteor Session variable. + * + * @param sessionKey - The name of the session variable + * @return An object with a single function bind - to bind to that variable. + */ + session(sessionKey: string): { + /** + * @param scope - The scope the document will be bound to. + * @param model - The name of the scope's model variable that the document will be bound to. + */ + bind: (scope: IScope, model: string) => void; + }; + } + + /** + * An object that connects a Meteor Object to an AngularJS scope variable. + * + * The object contains also all the properties from the generic type T, + * unfortunately TypeScript doesn't at the moment allow to extend a generic type (see https://github.com/Microsoft/TypeScript/issues/2225 for details and updates). + * For a workaround, you'll need to implement an interface which will merge AngularMeteorObject together with T and cast it, like this: + * + * interface TodoAngularMeteorObject extends ITodo, AngularMeteorObject { } + * var todo = $meteor.object(TodoCollection, 'TodoID'); + */ + interface AngularMeteorObject { + /** + * @param [doc] - The doc to save to the Meteor Object. If nothing is passed, the method saves everything in the AngularMeteorObject as is. + * - Unchanged properties will be overridden with their existing values, which may trigger hooks. + * - If doc is passed, the method only updates the Meteor Object with the properties passed, and no other changes will be saved. + * + * @return Returns a promise with an error in case for an error or a number of successful docs changed in case of success. + */ + save(doc?: T): angular.IPromise; + + /** + * Reset the current value of the object to the one in the server. + */ + reset(): void; + + /** + * Returns a copy of the AngularMeteorObject with all the AngularMeteor-specific internal properties removed. + * The returned object is then safe to use as a parameter for method calls, or anywhere else where the data needs to be converted to JSON. + */ + getRawObject(): T; + } + + /** + * An object that connects a Meteor Collection to an AngularJS scope variable + */ + interface AngularMeteorCollection extends AngularMeteorCollection2 { } + + /** + * An object that connects a Meteor Collection to an AngularJS scope variable, + * but can use a differen type for updates. + */ + interface AngularMeteorCollection2 extends Array { + /** + * @param [docs] - The docs to save to the Meteor Collection. + * - If the docs parameter is empty, the method saves everything in the AngularMeteorCollection as is. + * - If an object is passed, the method pushes that object into the AngularMeteorCollection. + * - If an array is passed, the method pushes all objects in the array into the AngularMeteorCollection. + */ + save(docs?: U|U[]): void; + + /** + * @param [keys] - The keys of the object to remove from the Meteor Collection. + * - If nothing is passed, the method removes all the documents from the AngularMeteorCollection. + * - If an object is passed, the method removes the object with that key from the AngularMeteorCollection. + * - If an array is passed, the method removes all objects that matches the keys in the array from the AngularMeteorCollection. + */ + remove(keys?: string|number|string[]|number[]): void; + + /** + * A shorten (Syntactic sugar) function for the $meteor.subscribe function. + * Takes only one parameter and not returns a promise like $meteor.subscribe does. + * + * @param subscriptionName - The subscription name to subscribe to. Exactly like the first parameter in $meteor.subscribe service. + */ + subscribe(subscriptionName:string): AngularMeteorCollection2; + } + + interface ILoginWithExternalService { + (options: Meteor.LoginWithExternalServiceOptions): angular.IPromise; + } + + interface ReactiveResult { } +}