{"id":28025974,"url":"https://github.com/codercatdev/nativescript-next-workshop-lab3-start","last_synced_at":"2025-10-19T18:20:51.784Z","repository":{"id":31432316,"uuid":"34995881","full_name":"codercatdev/NativeScript-NEXT-Workshop-Lab3-Start","owner":"codercatdev","description":"NativeScript-NEXT-Workshop-Lab3-Start","archived":false,"fork":false,"pushed_at":"2015-05-03T18:26:02.000Z","size":5082,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-05-11T04:54:28.835Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codercatdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-03T18:19:29.000Z","updated_at":"2016-02-26T20:37:51.000Z","dependencies_parsed_at":"2022-08-29T08:51:25.078Z","dependency_job_id":null,"html_url":"https://github.com/codercatdev/NativeScript-NEXT-Workshop-Lab3-Start","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codercatdev%2FNativeScript-NEXT-Workshop-Lab3-Start","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codercatdev%2FNativeScript-NEXT-Workshop-Lab3-Start/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codercatdev%2FNativeScript-NEXT-Workshop-Lab3-Start/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codercatdev%2FNativeScript-NEXT-Workshop-Lab3-Start/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codercatdev","download_url":"https://codeload.github.com/codercatdev/NativeScript-NEXT-Workshop-Lab3-Start/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253518958,"owners_count":21921082,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-11T04:54:36.048Z","updated_at":"2025-10-19T18:20:46.738Z","avatar_url":"https://github.com/codercatdev.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 2015 TelerikNEXT NativeScript {N} Workshop - Lab #3\nContributors: [Clark Sell](http://csell.net) \u0026 [TJ VanToll](http://tjvantoll.com/) \u0026 Sebastian Witalec. You can find us on Twitter at: [@csell5](https://twitter.com/csell5), [@tjvantoll](https://twitter.com/tjvantoll), [@sebawita](https://twitter.com/sebawita)\n\nTags: TelerikNEXT, NativeScript, {N}, JavaScript, CSS3, iOS, Android\n\n## What are we learning?\n\nIn this lab we are going to learn about:\n\n* Implementing MVVM with {N}\n\t* How to build an Observable ViewModel\n\t* How to connect a View to its ViewModel\n\t* (optional) More of the same but with TypeScript\n* How to use Analytics\n\t* Creating Analytics Project\n\t* Linking it with your app\n\t* Tracking feature use and Exceptions\n \n## Getting Setup\n\nIn the first lab you learned how to [clone an existing project](https://github.com/NativeScript/NativeScript-NEXT-Workshop/tree/master/labs/Lab-1#step-1-clone-the-repo) in AppBuilder. For this lab here is the starting and ending points:\n\n{N} lab projects in GitHub:\n* [Starting Project](https://github.com/NativeScript/NativeScript-NEXT-Workshop-Lab3-Start)\n* [Finished Project](https://github.com/NativeScript/NativeScript-NEXT-Workshop-Lab3-Finish)\n\n## The Lab\n\nThe lab is divided into few sections:\n* Steps 1-10 will take you through an excersise of converting create-meme page to use MVVM, by moving the app logic from `create-meme.js` to `View-model.js`. The purpose of this excersise is to show you how you can migrate from one approach (app logic mixed with view's code-behind) to the other (app logic in the ViewModel) and to show you how a ViewModel should be strucutred.\n* Step 11 shows you how a ViewModel looks when implemented in TypeScript. TypeScript is a great language that can speed up and simplyfy the process building JavaScript modules. The best thing is that TypeScript compiles into JavaScript, so you are never missing out.\n* Steps 12-17 show you how to add app usage Analytics to your app, so that you could see how often/which features people use.\n\n### Step #0.1- Examine the ViewModel of the Create Meme page\nGo to app-\u003ecomponents-\u003ecreate-meme folder and open `view-model.js` file\nHere you can see the code to create a new ViewModel\n\n```JavaScript\nvar observable = require(\"data/observable\");\nvar viewModel = new observable.Observable();\n```\n\t\nAnd finally the ViewModel is exposed from the module, through:\n\n```JavaScript\nexports.viewModel = viewModel;\n```\n\nThere are also a few placeholders prepared to implement the functionality of the ViewModel:\n\n**MODULE IMPORTS**: This is where we're going to import the required modules\n\n**VIEW MODEL METHOD PLACEHOLDERS**: This is where we're going to implement the methods of the ViewModel\n\n**EVENT HANDLER**: this is where we're going to add on the property change handler\n\n### Step #0.2 - Examine `create-meme.js` file\nGo to app-\u003ecomponents-\u003ecreate-meme folder and open `create-meme.js`file.\n\nHere we have:\n* `exports.loaded` - provides the code for the `loaded` event in the view. Here is where we initialise the page and add the `propertyChange` event,\n* `exports.navigatedTo` - provides the code for the `navigatedTo` event in the view. Here is were we retrieve the image passed in through `_page.navigationContext` and then reset the values to their default state,\n* `function refreshMeme()` - this function refreshes the MemeImage\n* `exports.saveLocally` - provides the code for the `tap` event of the Save button in the View `\u003cButton text=\"Save\" tap=\"saveLocally\" /\u003e`. It contains the necessary code to save the Meme to a file.\n* `exports.share` - provides the code for the `tap` event of the Save button in the View `\u003cButton text=\"Share\" tap=\"share\" /\u003e`. It contains the necessary code to share the Meme Image with other apps.\n* `function addRefreshOnChange()` - adds a change event handler, which is raised every time there is a change made to `_viewData` calling `refreshMeme()`, unless the event is raised by a change to `memeImage`.\n\nYou can note that the part of code begin with `exports.` are exposed to the View (`create-meme.xml`).\n\n### Step #1 - [view-model.js] Copy imports\n\nFirst we have to start by copying over the module imports from the beginning of create-meme.js.\n\nOpen create-meme.js and copy the first 5 lines (which import the required modules) and paste them in view-model.js just below the //---MODULE IMPORTS---// line.\n\nYou should get something like:\n\t\n```JavaScript\n//---MODULE IMPORTS---//\nvar imageManipulation = require(\"../image-manipulation/image-manipulation\");\nvar localStorage = require(\"../../shared/local-storage/local-storage\");\nvar socialShare = require(\"../social-share/social-share\");\nvar utilities = require(\"../../shared/utilities\");\nvar dialogsModule = require(\"ui/dialogs\");\n```\n\n### Step #2 - [view-model.js] Implement prepareNewMeme\n\nLet's start with implementing the method that should reset the ViewModel to default values and set the selectedImage as the basis for image manipulation.\nThis method will be called every time a new image is selected.\n\nLet's start with adding the `prepareNewMeme` function:\n\n```JavaScript\nviewModel.prepareNewMeme = function(selectedImage) {\n\n};\n```\n\nAnd now copy the contents of `exports.navigatedTo` into `prepareNewMeme`.\n\nFinally we need to tweak it a bit:\n\na) Change the following line that changes the selected image source from:\n\n```JavaScript\n_selectedImageSource = _page.navigationContext;\n```\n\nto:\n\n```JavaScript\nthis.selectedImage = selectedImage;\n```\n\nb) Change the following property assignments from\n\n```JavaScript\n_viewData.set(\"topText\", \"\");\n_viewData.set(\"bottomText\", \"\");\n_viewData.set(\"fontSize\", 40);\n_viewData.set(\"isBlackText\", false);\n_viewData.set(\"memeImage\", _selectedImageSource);\n```\n\nto:\n\n```JavaScript\nthis.set(\"topText\", \"\");\nthis.set(\"bottomText\", \"\");\nthis.set(\"fontSize\", 40);\nthis.set(\"isBlackText\", false);\nthis.set(\"memeImage\", selectedImage);\n```\n\nc) And change the unique image name assignment from\n\n```JavaScript\n_uniqueImageName = utilities.generateUUID() + \".png\";\n```\n\nto:\n\n```JavaScript\nthis.uniqueImageName = utilities.generateUUID() + \".png\";\n```\n\nYou probably noticed the pattern: change `_viewData` to `this` and place any variable inside `this`.\nAlso we no longer reference the page, as the ViewModel shouldn't be aware of the view.\n\nYou are probably thinking what is the difference between `this.x = 1` and `this.set(\"x\", 1);`, and when to use which.\nThis is rather straight forward. \nThe first approach is for internal use only: any changes to it shouldn't affect the UI, while the second one (`set()`), should. The UI components should be able to bind to the view model and any changes to it should propagate to the View.\n\nHere is what your `prepareNewMeme` function should look like:\n\n```JavaScript\n//Prepare New Meme\nviewModel.prepareNewMeme = function(selectedImage) {\n\tthis.selectedImage = selectedImage;\n\n\tthis.set(\"topText\", \"\");\n\tthis.set(\"bottomText\", \"\");\n\tthis.set(\"fontSize\", 40);\n\tthis.set(\"isBlackText\", false);\n\tthis.set(\"memeImage\", selectedImage);\n\n\tthis.uniqueImageName = utilities.generateUUID() + \".png\";\n};\n```\n\n### Step #3 - [view-model.js] Refresh Meme\nFrom here things get even easier.\n\nFirst add a `refreshMeme` function to the ViewModel\n\n```JavaScript\nviewModel.refreshMeme = function () {\n\t\n};\n```\n\nNow copy the contents from the `refreshMeme` function in edit-meme.js.\nReplace `_viewData` with `viewModel` and replace `_selectedImageSource` with `viewModel.selectedImage`. Finally you should end up with something like:\n\n```JavaScript\n//Refresh Meme\nviewModel.refreshMeme = function () {\n\tvar image = imageManipulation.addText(viewModel.selectedImage, viewModel.topText, viewModel.bottomText, viewModel.fontSize, viewModel.isBlackText);\n\n\tviewModel.set(\"memeImage\", image);\n};\n```\n\n### Step #4 - [view-model.js] Save Locally\n\nNext, let's tackle the `saveLocally` function. First add a `saveLocally` function to the ViewModel:\n\n```JavaScript\nviewModel.saveLocally = function () {\n\t\n};\n```\n\nNow copy the contents of `saveLocally` from edit-meme.js.\n\n`refreshMeme()` is a function of the viewModel (not a global function), so we have to refer to it as `this.refreshMeme()`.\n\nAlso the two parameters passed to `localStorage.saveLocally` come from the ViewModel, therefore we should change it to `this.uniqueImageName` and `this.memeImage`. As a result your `saveLocally` function should look like this:\n\n```JavaScript\n//Save Locally\nviewModel.saveLocally = function () {\n\tthis.refreshMeme();\n\tvar saved = localStorage.saveLocally(this.uniqueImageName, this.memeImage);\n\n\tif (!saved) {\n\t\tconsole.log(\"New meme not saved....\");\n\t} else {\n\t\tvar options = {\n\t\t\ttitle: \"Meme Saved\",\n\t\t\tmessage: \"Congratulations, Meme Saved!\",\n\t\t\tokButtonText: \"OK\"\n\t\t};\n\n\t\tdialogsModule.alert(options);\n\t}\n};\n```\n\n### Step #5 - [view-model.js] Share\nThis is the last and the easiest of the functions that we need to implement. So this will be without a surprise that you should get something like:\n\n```JavaScript\n//Share\nviewModel.share = function() {\n\tsocialShare.share(this.memeImage);\n}\n```\n\n### Step #6 - [view-model.js] On Property Changed\n\nAdd following code to the **//---EVENT HANDLER---//** section:\n\n```JavaScript\n//---EVENT HANDLER---//\nviewModel.addEventListener(observable.knownEvents.propertyChangeEvent, function(changes) {\n\t//skip if memeImage changes\n\tif (changes.propertyName === \"memeImage\") {\n\t\treturn;\n\t}\n\t\n\t//Call refresh meme, but make sure it doesn't get called more often than every 200ms\n\tviewModel.refreshMeme();\n});\n```\n\nA few things worth noting:\nYou don't need to place the above code into another function. This piece of code will only execute once throughout the lifecycle of the app. This is because calling `require` on a module definition is only executed once, regardless of how many times it's required.\n\nWe call `viewModel.refreshMeme();` and not `this.refreshMeme();` because the `propertyChangeEvent` triggers a call back from a different scope, so `this` wouldn't be referring to the viewModel anymore.\n\nThe same `propertyChangeEvent` is raised regardless of which property of the ViewModel changes. This is why there is a bit of code that checks if the `propertyName` is \"memeImage\".\n\n#### Step #7 - [create-meme.js] Add ViewModel\nOpen create-meme.js\n\nWe no longer need `var _viewData`, as this is replaced by our ViewModel. So replace\n\n```JavaScript\nvar _viewData = new observable.Observable();\n```\n\t\nwith\n\n```JavaScript\nvar createMemeViewModel = require(\"./view-model\").viewModel;\n```\n\nLet's explain the code above. `require(\"./view-model\")` retrieves our ViewModel Module. While `.viewModel` retrieves the exported viewModel instance.\n\n#### Step #8 - [create-meme.js] loaded and navigatedTo\n\nNow let's update the `exports.loaded` function. The `bindingContext` should be set to `createMemeViewModel` and we no longer need the call to `addRefreshOnChange` function, as that is handled inside the ViewModel.\n\nYou should end up with:\n\n```JavaScript\nexports.loaded = function(args) {\n\t_page = args.object;\n\t_page.bindingContext = createMemeViewModel;\n};\n```\n\nNow let's update the `exports.navigatedTo` function. Everything that is happening in this function is handled by `prepareNewMeme`.\n\nSo let's replace its content with a call to the viewModel.\n\nYou should get something like:\n\n```JavaScript\nexports.navigatedTo = function(args) {\n\t//grab the image from the navigation context.\n\tvar selectedImage = _page.navigationContext;\n\tcreateMemeViewModel.prepareNewMeme(selectedImage);\n};\n```\n\n#### Step #9 - [create-meme.js] Clean up\nOnce we added the ViewModel, we no longer need the require imports at the top of the file, so delete them all, leaving only the one that loads the ViewModel.\n\nAlso we no longer need:\n\n* The `refreshMeme()` function, \n* `exports.saveLocally()`,\n* `exports.share()` and\n* `function addRefreshOnChange()`\n\nFinally, `var _page` is the only variable that we are still using, so delete the rest.\n\nThe final version of create-meme.js should look as follows:\n\n```JavaScript\nvar createMemeViewModel = require(\"./view-model\").viewModel;\n\nvar _page;\n\nexports.loaded = function(args) {\n\t_page = args.object;\n\t_page.bindingContext = createMemeViewModel;\n};\n\nexports.navigatedTo = function(args) {\n\t//grab the image from the navigation context.\n\tvar selectedImage = _page.navigationContext;\n\tcreateMemeViewModel.prepareNewMeme(selectedImage);\n};\n```\n\n#### Step #10 [create-meme.xml] Binding reference update\nThe final step is to setup bindings on the buttons.\n\nOpen create-meme.xml and find the 2 button definitions. Change the `tap` event's bindings to the ViewModel functions, by adding `{{ }}` around the name of the function.\n\nThe buttons should look as follows:\n\n```xml\n\u003cButton text=\"Save\" tap=\"{{ saveLocally }}\" /\u003e\n\u003cButton text=\"Share\" tap=\"{{ share }}\" /\u003e\n```\n\nHere's a quick explanation:\n`tap=\"{{ saveLocally }}\"` means: on tap -\u003e go to the ViewModel -\u003e and call `viewModel.saveLocally`\n\n#### Step #11 [view-model-v2.ts] ViewModel using TypeScript\n\nRight click on the **create-meme** folder -\u003e Add -\u003e New File. Select TypeScript, and call it view-model-v2.ts\n\nNow paste and save the following code:\n\n```JavaScript\nmodule MemeViewModel {\nvar imageManipulation = require(\"../image-manipulation/image-manipulation\");\nvar localStorage = require(\"../../shared/local-storage/local-storage\");\nvar socialShare = require(\"../social-share/social-share\");\nvar utilities = require(\"../../shared/utilities\");\nvar dialogsModule = require(\"ui/dialogs\");\nvar observable = require(\"data/observable\");\n\nexport class CreateMemeViewModel extends observable.Observable {\n\tpublic topText: string = \"\";\n\tpublic bottomText: string = \"\";\n\tpublic fontSize: number = 40;\n\tpublic isBlackText: boolean = false;\n\tprivate selectedImage: any;\n\tpublic memeImage: any;\n\tprivate uniqueImageName: string;\n\t\n\tpublic prepareEditor(image: any) {\n\t\tthis.selectedImage = image;\n\n\t\tthis.set(\"topText\", \"\");\n\t\tthis.set(\"bottomText\", \"\");\n\t    this.set(\"fontSize\", 40);\n\t    this.set(\"isBlackText\", false);\n\t\tthis.set(\"memeImage\", image);\n\t\t\n\t\tthis.refreshUniqueName();\n\t}\n\t\n\tpublic refreshMeme() {\n\t\tvar image = imageManipulation.addText(\n\t\t\tthis.selectedImage, this.topText, this.bottomText, this.fontSize, this.isBlackText);\n\n\t\tthis.set(\"memeImage\", image);\n\t}\n\t\n\tpublic addRefreshOnChange() {\n\t\tvar viewModel = this;\n\t\t\n\t\tthis.addEventListener(observable.knownEvents.propertyChange, function(changes) {\n\t\t\t//skip if memeImage changes\n\t\t\tif(changes.propertyName === \"memeImage\")\n\t\t\t\treturn;\n\t\t\t\n\t\t\tviewModel.refreshMeme();\n        });\n    }\n\n    refreshUniqueName() {\n\t\tthis.uniqueImageName = utilities.generateUUID() + \".png\";\n    }\n\n\tpublic saveLocally() {\n\t\tthis.refreshMeme();\n\t\tvar saved = localStorage.saveLocally(this.uniqueImageName, this.memeImage);\n\n\t\tif (!saved) {\n\t\t\tconsole.log(\"New meme not saved....\");\n\t\t} else {\n\t\t\tvar options = {\n\t\t\t\ttitle: \"Meme Saved\",\n\t\t\t\tmessage: \"Congratulations, Meme Saved!\",\n\t\t\t\tokButtonText: \"OK\"\n\t\t\t};\n\n\t\t\tdialogsModule.alert(options);\n\t\t}\n    }\n\n    public share() {\n        socialShare.share(this.memeImage);\n    }\n}\n\nexport var viewModel = new CreateMemeViewModel();\nviewModel.addRefreshOnChange();\n}\n```\n\nNow expand view-model-v2.ts (you might need to right click on it and select \"Compile to JavaScript\" first) and open the .js file. This is the code that is generated from the TypeScript version.\n\n#### Step #12 - Create Analytics project\n\nGo back to Telerik Platform and create a new Analytics project and make sure to select **JavaScript** as the target platform.\n\n[Instructions: How to Create a new Analytics Project] (http://docs.telerik.com/platform/help/projects/create/analytics-project)\n\nOnce the new project is ready go to Getting Started -\u003e SDK. Note the string passed into the `createSettings` function:\n\n```JavaScript\nvar settings = _eqatec.createSettings(\"1234567890asdfghjkl23456789\");\n```\n\nThis is your Analytics Key. Make a copy of it, we will use it in a moment.\n\n#### Step #13 - Add Analytics Key\n\nGo back to your NativeScript project and then open app/shared/analytics.js.\nPaste your Analytics key where it says `'analytics-key-here'`\n\nThis will link the Analytics Monitor with your Analytics project.\n\n#### Step #14 - Add Analytics Module to create-meme\n\nOpen view-model.js in create-meme folder and a line to require the analytics module:\n\n```JavaScript\nvar analyticsMonitor = require(\"../../shared/analytics\");\n```\n\nFrom now you can:\n\n* Track Feature Usage\n```JavaScript\t\nanalyticsMonitor.trackFeature('MyCategory.MyFeature');`\n```\n* Track Used Values\n```JavaScript\nanalyticsMonitor.trackFeatureValue('MyCategory.MyValue', 1000);\n```\n* Track How Long It Takes To Run Something\n```JavaScript\nanalyticsMonitor.trackFeatureStart('MyCategory.MyFeature');\n//Do something here\nanalyticsMonitor.trackFeatureStop('MyCategory.MyFeature');\n```\n\n* Track Raised Exceptions\n```JavaScript\nanalyticsMonitor.trackException(new Error('some error'), 'some error message');\n```\nor\n```JavaScript\ntry {\n\t//do something\n} catch (exception) {\n\tanalyticsMonitor.trackException(exception, 'some error message');\n}\n```\n\n**IMPORTANT**\n\nThere is a 1-day delay for the feature tracking to appear in the Analytics Portal, so please be patient.\nHowever you can see current and recently connected devices at Developer Reports -\u003e Live\n\n#### Step #15 Tracking the usage of saveLocally() and share()\n\nGo to the `viewModel.saveLocally()` function and insert the following code as the first line of the function:\n\n```JavaScript\nanalyticsMonitor.trackFeature(\"CreateMeme.SaveLocally\");\n```\n\nNow do the same for `viewModel.share()`:\n\n```JavaScript\nanalyticsMonitor.trackFeature(\"CreateMeme.Share\");\n```\n\nAt the end your function should look like this:\n\n```JavaScript\n//Save Locally\nviewModel.saveLocally = function() {\n\tanalyticsMonitor.trackFeature(\"CreateMeme.SaveLocally\");\n\tthis.refreshMeme();\n\tvar saved = localStorage.saveLocally(this.uniqueImageName, this.memeImage);\n\n\tif (!saved) {\n\t\tconsole.log(\"New meme not saved....\");\n\t} else {\n\t\tvar options = {\n\t\t\ttitle: \"Meme Saved\",\n\t\t\tmessage: \"Congratulations, Meme Saved!\",\n\t\t\tokButtonText: \"OK\"\n\t\t};\n\n\t\tdialogsModule.alert(options);\n\t}\n}\n\n//Share\nviewModel.share = function() {\n\tanalyticsMonitor.trackFeature(\"CreateMeme.Share\");\n\tsocialShare.share(this.memeImage);\n}\n```\n\n#### Step #16 Exception tracking for refreshMeme\n\nGo to the `viewModel.refreshMeme()` and wrap its content with a try/catch (like it is shown in Step 14) and add an error message inside the `trackException` call.\n\nYou should get something like this:\n\n```JavaScript\nviewModel.refreshMeme = function () {\n\ttry {\n\t\tvar image = imageManipulation.addText(viewModel.selectedImage, viewModel.topText, viewModel.bottomText, viewModel.fontSize, viewModel.isBlackText);\n\t\tviewModel.set(\"memeImage\", image);\n\t} catch (exception) {\n\t\tanalyticsMonitor.trackException(exception, 'Failed Refreshing Meme Image');\n\t}\n};\n```\n\n#### Step #17 Experiment with Analytics\n\nIt is up to you now to go around the project and experiment with Analytics. Try to look for other opportunities within this project to track features or exceptions. \n\nYou can also check **app/components/home/home.js** and **app/components/create-template/create-template.js** and look for commented out code that uses `analyticsMonitor`.\n\n## Resource List\n\n* [Observable](http://docs.nativescript.org/ApiReference/data/observable/HOW-TO.html)\n* [Analytics Overview] (http://docs.telerik.com/platform/analytics/getting-started/introduction)\n* [Analytics SDK](http://docs.telerik.com/platform/analytics/sdk/js/classes/AnalyticsMonitor.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodercatdev%2Fnativescript-next-workshop-lab3-start","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodercatdev%2Fnativescript-next-workshop-lab3-start","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodercatdev%2Fnativescript-next-workshop-lab3-start/lists"}