diff --git a/DEVELOPER.md b/DEVELOPER.md index fcd2cfc30..a71660f6d 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -96,6 +96,10 @@ The `@Cordova` decorator has a few more options now. You need to run `npm run build_bundle` in the `ionic-native` project, this will create a `dist` directory. Then, you must go to your ionic application folder and replace your current `node_modules/ionic-native/dist/` with the newly generated one. +### Cleaning the code + +You need to run `gulp tslint` to analyze the code and ensure it's consitency with the repository style. Fix any errors before submitting a PR. + ### 'Wrapping' Up That's it! The only thing left to do is rigorously document the plugin and it's usage. Take a look at some of the other plugins for good documentation styles. diff --git a/README.md b/README.md index 3fcc8b0ee..2da5cf8ed 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,7 @@ Geolocation.getCurrentPosition().then(pos => { console.log('lat: ' + pos.coords.latitude + ', lon: ' + pos.coords.longitude); }); -let watch = Geolocation.watchPosition(); -watch.subscribe(pos => { +let watch = Geolocation.watchPosition().subscribe(pos => { console.log('lat: ' + pos.coords.latitude + ', lon: ' + pos.coords.longitude); }); diff --git a/scripts/docs/dgeni-config.js b/scripts/docs/dgeni-config.js index 2281a052a..a0cf4217c 100644 --- a/scripts/docs/dgeni-config.js +++ b/scripts/docs/dgeni-config.js @@ -22,6 +22,7 @@ module.exports = function(currentVersion) { .processor(require('./processors/jekyll')) .processor(require('./processors/remove-private-members')) .processor(require('./processors/hide-private-api')) +.processor(require('./processors/collect-inputs-outputs')) // for debugging docs // .processor(function test(){ diff --git a/scripts/docs/processors/collect-inputs-outputs.js b/scripts/docs/processors/collect-inputs-outputs.js new file mode 100644 index 000000000..c8b382e65 --- /dev/null +++ b/scripts/docs/processors/collect-inputs-outputs.js @@ -0,0 +1,62 @@ +module.exports = function collectInputsOutputs() { + return { + + $runBefore: ['rendering-docs'], + $process: function(docs) { + docs.forEach(function(doc) { + + if (doc.members && doc.members.length) { + var members = []; + var inputs = []; + var outputs = []; + + memberLoop: + for (var i in doc.members) { + + // identify properties to differentiate from methods + if (typeof doc.members[i].parameters == 'undefined') { + doc.members[i].isProperty = true; + } + + if (doc.members[i].decorators && doc.members[i].decorators.length) { + + decoratorLoop: + for (var ii in doc.members[i].decorators) { + + if (doc.members[i].decorators[ii].name == 'Input') { + inputs.push(parseMember(doc.members[i])); + continue memberLoop; + } + if (doc.members[i].decorators[ii].name == 'Output') { + outputs.push(parseMember(doc.members[i])); + continue memberLoop; + } + } + // not an input or output, must be a plain member + members.push(doc.members[i]); + } else { + members.push(doc.members[i]); + }; + } + + // update doc with pruned members list and add inputs and outputs + doc.members = members; + doc.inputs = inputs; + doc.outputs = outputs; + } + + function parseMember(member) { + member.type = member.content.substring( + member.content.indexOf('{') + 1, + member.content.indexOf('}') + ); + member.description = member.content.substring( + member.content.indexOf('}') + 1, + member.content.length + ); + return member; + } + }); + } + }; +}; diff --git a/scripts/docs/templates/common.template.html b/scripts/docs/templates/common.template.html index 4354f8c1d..886f84874 100644 --- a/scripts/docs/templates/common.template.html +++ b/scripts/docs/templates/common.template.html @@ -55,13 +55,13 @@ docType: "<$ doc.docType $>" <@- macro functionSyntax(fn) @> -<@- set sep = joiner(', ') -@> -<$ fn.name $>(<@- for param in fn.params @><$ sep() $> + <@- set sep = joiner(', ') -@> + <$ fn.name $><@- if not fn.isProperty @>(<@ endif -@><@- for param in fn.params @><$ sep() $> <@- if param.type.optional @>[<@ endif -@> <$ param.name $> <@- if param.type.optional @>]<@ endif -@> - <@ endfor @>) -<@ if fn.alias @>(alias: <$ fn.alias $>)<@ endif @> + <@ endfor @><@- if not fn.isProperty @>)<@ endif -@> + <@ if fn.alias @>(alias: <$ fn.alias $>)<@ endif @> <@ endmacro -@> <@ macro typeList(types) -@> @@ -203,7 +203,7 @@ docType: "<$ doc.docType $>" <@ endif @> <@- if doc.statics.length -@> -

Static Methods

+

Static Members

<@- for method in doc.statics @><@ if not method.internal @>

<$ functionSyntax(method) $>

@@ -244,7 +244,7 @@ docType: "<$ doc.docType $>" <@- if doc.members and doc.members.length @> -

Instance Methods

+

Instance Members

<@- for method in doc.members @>
diff --git a/src/index.ts b/src/index.ts index 68e4b0c62..1b04fb6d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -68,6 +68,7 @@ import {WebIntent} from './plugins/webintent'; export * from './plugins/googlemaps'; export * from './plugins/3dtouch'; export * from './plugins/httpd'; +export * from './plugins/contacts'; export { ActionSheet, AdMob, diff --git a/src/plugins/contacts.ts b/src/plugins/contacts.ts index 16299330a..17c8cb675 100644 --- a/src/plugins/contacts.ts +++ b/src/plugins/contacts.ts @@ -1,6 +1,7 @@ -import {Plugin, Cordova} from './plugin'; - -export interface ContactProperties { +import {Plugin, Cordova, InstanceProperty, CordovaInstance} from './plugin'; +declare var window: any, + navigator: any; +export interface IContactProperties { /** A globally unique identifier. */ id?: string; /** The name of this Contact, suitable for display to end users. */ @@ -10,13 +11,13 @@ export interface ContactProperties { /** A casual name by which to address the contact. */ nickname?: string; /** An array of all the contact's phone numbers. */ - phoneNumbers?: ContactField[]; + phoneNumbers?: IContactField[]; /** An array of all the contact's email addresses. */ - emails?: ContactField[]; + emails?: IContactField[]; /** An array of all the contact's addresses. */ addresses?: ContactAddress[]; /** An array of all the contact's IM addresses. */ - ims?: ContactField[]; + ims?: IContactField[]; /** An array of all the contact's organizations. */ organizations?: ContactOrganization[]; /** The birthday of the contact. */ @@ -24,45 +25,51 @@ export interface ContactProperties { /** A note about the contact. */ note?: string; /** An array of the contact's photos. */ - photos?: ContactField[]; + photos?: IContactField[]; /** An array of all the user-defined categories associated with the contact. */ - categories?: ContactField[]; + categories?: IContactField[]; /** An array of web pages associated with the contact. */ - urls?: ContactField[]; + urls?: IContactField[]; } - -export interface Contact extends ContactProperties { - /** - * Returns a new Contact object that is a deep copy of the calling object, with the id property set to null - */ - clone(): Contact; - /** - * Removes the contact from the device contacts database, otherwise executes an error callback with a ContactError object. - * @param onSuccess Success callback function invoked on success operation. - * @param onError Error callback function, invoked when an error occurs. - */ - remove( - onSuccess?: () => void, - onError?: (error: Error) => void): void; - /** - * Saves a new contact to the device contacts database, or updates an existing contact if a contact with the same id already exists. - * @param onSuccess Success callback function invoked on success operation with che Contact object. - * @param onError Error callback function, invoked when an error occurs. - */ - save( - onSuccess?: (contact: Contact) => void, - onError?: (error: Error) => void): void; +export class Contact { + private _objectInstance: any; + @InstanceProperty get id(): string {return; } + @InstanceProperty get displayName(): string {return; } + @InstanceProperty get nickname(): ContactName {return; } + @InstanceProperty get phoneNumbers(): string {return; } + @InstanceProperty get emails(): IContactField[] {return; } + @InstanceProperty get addresses(): ContactAddress[] {return; } + @InstanceProperty get ims(): IContactField[] {return; } + @InstanceProperty get organizations(): ContactOrganization[] {return; } + @InstanceProperty get birthday(): Date {return; } + @InstanceProperty get note(): string {return; } + @InstanceProperty get photos(): IContactField[] {return; } + @InstanceProperty get categories(): IContactField[] {return; } + @InstanceProperty get urls(): IContactField[] {return; } + constructor () { + this._objectInstance = navigator.contacts.create(); + } + clone(): Contact { + let newContact = new Contact(); + for (let prop in this) { + if (prop === 'id') return; + newContact[prop] = this[prop]; + } + return newContact; + } + @CordovaInstance() + remove(): Promise {return; } + @CordovaInstance() + save(): Promise {return; } } - -interface ContactError { +interface IContactError { /** Error code */ code: number; /** Error message */ message: string; } - declare var ContactError: { - new(code: number): ContactError; + new(code: number): IContactError; UNKNOWN_ERROR: number; INVALID_ARGUMENT_ERROR: number; TIMEOUT_ERROR: number; @@ -71,8 +78,7 @@ declare var ContactError: { NOT_SUPPORTED_ERROR: number; PERMISSION_DENIED_ERROR: number }; - -export interface ContactName { +export interface IContactName { /** The complete name of the contact. */ formatted?: string; /** The contact's family name. */ @@ -86,18 +92,20 @@ export interface ContactName { /** The contact's suffix (example Esq.). */ honorificSuffix?: string; } +export class ContactName implements IContactName { + private _objectInstance: any; + constructor(formatted?: string, familyName?: string, givenName?: string, middleName?: string, honorificPrefix?: string, honorificSuffix?: string) { + this._objectInstance = new window.ContactName(formatted, familyName, givenName, middleName, honorificPrefix, honorificSuffix); + } + @InstanceProperty get formatted(): string {return; } + @InstanceProperty get familyName(): string {return; } + @InstanceProperty get givenName(): string {return; } + @InstanceProperty get middleName(): string {return; } + @InstanceProperty get honorificPrefix(): string {return; } + @InstanceProperty get honorificSuffix(): string {return; } +} -declare var ContactName: { - /** Constructor for ContactName object */ - new(formatted?: string, - familyName?: string, - givenName?: string, - middleName?: string, - honorificPrefix?: string, - honorificSuffix?: string): ContactName -}; - -export interface ContactField { +export interface IContactField { /** A string that indicates what type of field this is, home for example. */ type: string; /** The value of the field, such as a phone number or email address. */ @@ -106,14 +114,17 @@ export interface ContactField { pref: boolean; } -declare var ContactField: { - /** Constructor for ContactField object */ - new(type?: string, - value?: string, - pref?: boolean): ContactField -}; +export class ContactField implements IContactField { + private _objectInstance: any; + constructor(type?: string, value?: string, pref?: boolean) { + this._objectInstance = new window.ContactField(type, value, pref); + } + @InstanceProperty get type(): string {return; } + @InstanceProperty get value(): string {return; } + @InstanceProperty get pref(): boolean {return; } +} -export interface ContactAddress { +export interface IContactAddress { /** Set to true if this ContactAddress contains the user's preferred value. */ pref?: boolean; /** A string indicating what type of field this is, home for example. */ @@ -132,19 +143,29 @@ export interface ContactAddress { country?: string; } -declare var ContactAddress: { - /** Constructor of ContactAddress object */ - new(pref?: boolean, - type?: string, - formatted?: string, - streetAddress?: string, - locality?: string, - region?: string, - postalCode?: string, - country?: string): ContactAddress -}; +export class ContactAddress implements IContactAddress { + private _objectInstance: any; + constructor (pref?: boolean, + type?: string, + formatted?: string, + streetAddress?: string, + locality?: string, + region?: string, + postalCode?: string, + country?: string) { + this._objectInstance = new window.ContactAddress(pref, type, formatted, streetAddress, locality, region, postalCode, country); + } + @InstanceProperty get pref(): boolean {return; } + @InstanceProperty get type(): string {return; } + @InstanceProperty get formatted(): string {return; } + @InstanceProperty get streetAddress(): string {return; } + @InstanceProperty get locality(): string {return; } + @InstanceProperty get region(): string {return; } + @InstanceProperty get postalCode(): string {return; } + @InstanceProperty get country(): string {return; } +} -export interface ContactOrganization { +export interface IContactOrganization { /** Set to true if this ContactOrganization contains the user's preferred value. */ pref?: boolean; /** A string that indicates what type of field this is, home for example. */ @@ -157,17 +178,20 @@ export interface ContactOrganization { title?: string; } -declare var ContactOrganization: { - /** Constructor for ContactOrganization object */ - new(pref?: boolean, - type?: string, - name?: string, - department?: string, - title?: string): ContactOrganization -}; +export class ContactOrganization implements IContactOrganization { + private _objectInstance: any; + constructor () { + this._objectInstance = new window.ContactOrganization(); + } + @InstanceProperty get pref(): boolean {return; } + @InstanceProperty get type(): string {return; } + @InstanceProperty get name(): string {return; } + @InstanceProperty get department(): string {return; } + @InstanceProperty get title(): string {return; } +} /** Search options to filter navigator.contacts. */ -interface ContactFindOptions { +export interface IContactFindOptions { /** The search string used to find navigator.contacts. */ filter?: string; /** Determines if the find operation returns multiple navigator.contacts. */ @@ -176,35 +200,34 @@ interface ContactFindOptions { desiredFields?: string[]; } -declare var ContactFindOptions: { - /** Constructor for ContactFindOptions object */ - new(filter?: string, - multiple?: boolean, - desiredFields?: string[]): ContactFindOptions -}; - -declare var Contact: { - new(): Contact -}; - +export class ContactFindOptions implements IContactFindOptions { + private _objectInstance: any; + constructor () { + this._objectInstance = new window.ContactFindOptions(); + } + @InstanceProperty get filter(): string {return; } + @InstanceProperty get multiple(): boolean {return; } + @InstanceProperty get desiredFields(): any {return; } + @InstanceProperty get hasPhoneNumber(): boolean {return; } +} /** * @name Contacts * @description * Access and manage Contacts on the device. * - * Requires plugin: `cordova-plugin-contacts` - * For full info, please see the [Cordova Contacts plugin docs](https://github.com/apache/cordova-plugin-contacts) - * * @usage * * ```js - * import {Contacts} from 'ionic-native'; + * import {Contact} from 'ionic-native'; * * * - * Contacts.create({ - * displayName: "Mr. Ionitron" - * }).then((contact) => {}, (err) => {}) + * let contact = new Contact(); + * contact.displayName = "Mr. Ionitron"; + * contact.save().then( + * () => console.log("Contact saved!", contact), + * (error: any) => console.error("Error saving contact.", error) + * ); * ``` * * @@ -215,19 +238,9 @@ declare var Contact: { repo: 'https://github.com/apache/cordova-plugin-contacts' }) export class Contacts { - /** - * Create a new Contact object. - * - * @param options {Object} Object whose properties the created Contact should have. - * @return {Contact} Returns the created contact - */ - @Cordova({ - sync: true - }) - static create(options: ContactProperties) { - return new Contact(); - }; - + static create(): Contact { + return new Contact(); + } /** * Search for contacts in the Contacts list. * @@ -250,12 +263,10 @@ export class Contacts { errorIndex: 2 }) static find(fields: string[], options?: any): Promise { return; } - - /** * Select a single Contact. * @return Returns a Promise that resolves with the selected Contact */ @Cordova() - static pickContact(): Promise { return; } -} + static pickContact(): Promise {return; } +} \ No newline at end of file diff --git a/src/plugins/deeplinks.ts b/src/plugins/deeplinks.ts index 5bb060f69..d27d7da67 100644 --- a/src/plugins/deeplinks.ts +++ b/src/plugins/deeplinks.ts @@ -5,14 +5,19 @@ export interface DeeplinkMatch { /** * The route info for the matched route */ - routeInfo: any; + $route: any; /** - * The arguments passed to the route through GET params along with + * Any arguments passed either through route parameters or GET parameters + */ + $args: any; + + /** + * The deeplink object processed from the plugin, along with any * any internal native data available as "extras" at the time * the route was matched (for example, Facebook sometimes adds extra data) */ - args: any; + $link: any; } /** diff --git a/src/plugins/media.ts b/src/plugins/media.ts index 37c7d4715..208987185 100644 --- a/src/plugins/media.ts +++ b/src/plugins/media.ts @@ -1,4 +1,5 @@ import {CordovaInstance, Plugin} from './plugin'; +import {Observable} from 'rxjs/Observable'; declare var Media: any; /** * @name MediaPlugin @@ -8,23 +9,47 @@ declare var Media: any; * import {MediaPlugin} from 'ionic-native'; * * - * ... * - * - * // Playing a file + * // Create a MediaPlugin instance. Expects path to file or url as argument * var file = new MediaPlugin("path/to/file.mp3"); * + * // Catch the Success & Error Output + * // Platform Quirks + * // iOS calls success on completion of playback only + * // Android calls success on completion of playback AND on release() + * file.init.then(() => { + * console.log("Playback Finished"); + * }, (err) => { + * console.log("somthing went wrong! error code: "+err.code+" message: "+err.message); + * }); + * * // play the file * file.play(); * - * // skip to 10 seconds + * // pause the file + * file.pause(); + * + * // get current playback position + * file.getCurrentPosition().then((position) => { + * console.log(position); + * }); + * + * // get file duration + * file.getDuration().then((duration) => { + * console.log(position); + * }); + * + * // skip to 10 seconds (expects int value in ms) * file.seekTo(10000); * - * // stop plying the file + * // stop playing the file * file.stop(); * - * - * ... + * // release the native audio resource + * // Platform Quirks: + * // iOS simply create a new instance and the old one will be overwritten + * // Android you must call release() to destroy instances of media when you are done + * file.release(); * * // Recording to a file * var newFile = new MediaPlugin("path/to/file.mp3"); @@ -52,6 +77,8 @@ export class MediaPlugin { // Properties private _objectInstance: any; + status: Observable; + init: Promise; // Methods /** @@ -59,8 +86,12 @@ export class MediaPlugin { * @param src {string} A URI containing the audio content. */ constructor (src: string) { - // TODO handle success, error, and status - this._objectInstance = new Media(src); + let res, rej, next; + this.init = new Promise((resolve, reject) => {res = resolve; rej = reject; }); + this.status = new Observable((observer) => { + next = data => observer.next(data); + }); + this._objectInstance = new Media(src, res, rej, next); } /** diff --git a/src/plugins/plugin.ts b/src/plugins/plugin.ts index f5b385690..c6b22894b 100644 --- a/src/plugins/plugin.ts +++ b/src/plugins/plugin.ts @@ -141,10 +141,12 @@ function wrapObservable(pluginObj: any, methodName: string, args: any[], opts: a return () => { try { - if (opts.clearWithArgs) { - return get(window, pluginObj.pluginRef)[opts.clearFunction].apply(pluginObj, args); + if (opts.clearFunction) { + if (opts.clearWithArgs) { + return get(window, pluginObj.pluginRef)[opts.clearFunction].apply(pluginObj, args); + } + return get(window, pluginObj.pluginRef)[opts.clearFunction].call(pluginObj, pluginResult); } - return get(window, pluginObj.pluginRef)[opts.clearFunction].call(pluginObj, pluginResult); } catch (e) { console.warn('Unable to clear the previous observable watch for', pluginObj.name, methodName); console.error(e); @@ -323,13 +325,13 @@ export function CordovaProperty(target: Function, key: string, descriptor: Typed * @param descriptor * @constructor */ -export function InstanceProperty(target: Function, key: string, descriptor: TypedPropertyDescriptor) { +export function InstanceProperty(target: any, key: string, descriptor: TypedPropertyDescriptor) { descriptor.get = function() { return this._objectInstance[key]; }; descriptor.set = function(...args: any[]) { - return this._objectInstance[key] = args[0]; + this._objectInstance[key] = args[0]; }; return descriptor;