Using ACE editor with Angular - splitting the value by newline - angularjs

I am using the ace-ui directive with angular to use the ACE code editor in my application. It works great so far, but I am having one little caveat.
I need code that is written to get stored as an array of strings, separated by the \n character. This is easy to do at first, I just do this;
In the controller, I have a $scope.$watch on the actual editor text, it looks like this;
app.controller("EditorCtrl", [ "$scope", function($scope){
$scope.Model = {
// other properties
Scripting: ""
};
$scope.Editor = {
// other properties
Scripting: ""
};
$scope.aceLoaded = function(editor){
ace.require("ace/ext/language_tools");
// setup the defaults
editor.setTheme("ace/theme/chrome");
editor.getSession().setMode("ace/mode/javascript");
// options
editor.setOptions({
showGutter: true,
showPrintMargin: false,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
fontSize: '14px',
fontFamily: 'Consolas'
});
}
$scope.$watch("Editor.Scripting", function(n, o){
$scope.Model.Scripting = n.split("\n");
});
});
This seems to work fine, but I don't think it is right to have Scripting twice. Is there a simpler way to accomplish this that will not require $watch or will not require having to proxy it through a second variable on the $scope? I've tried binding to $scope.Model.Scripting directly, but that caused some strange behavior when it came to loading an object that already had scripting on it.
The HTML for this looks like ..
<div ui-ace="{ onLoad: aceLoaded }" data-ng-model="Editor.Scripting"></div>
{{ Model.Scripting }}

You do not describe why do you need this, but if you are not going to modify that array, and you always want the latest version, you could use internal lines array used by ace editor.session.doc.$lines, which would be faster as it won't need to create new array on every change.

Related

ng-model versus getElementById

I am reading the book "Pro AngularJS" by Adam Freeman, Apress.
He creates an app that is a to-do list and you can add your own things on the list. In the part "Responding to User Interaction", page 34
Here is the model
var model = {
user: "Adam",
items: [{ action: "Buy Flowers", done: false },
{ action: "Get Shoes", done: false },
{ action: "Collect Tickets", done: true },
{ action: "Call Joe", done: false }]
};
the app
var todoApp = angular.module("todoApp", []);
the controller
todoApp.controller("ToDoCtrl", function ($scope) {
$scope.todo = model;
$scope.addNewItem = function (actionText) {
$scope.todo.items.push({ action: actionText, done: false });
}
});
the input field
<input class="form-control" ng-model="actionText" />
and the button that adds new things in the model
<button ng-click="addNewItem(actionText)">Add</button>
And then he comments this line <input class="form-control" ng-model="actionText" /> (pp 36)
I have specified the name of a property for the directive to update
that is not part of the model. The ng-model directive will dynamically
create the property for me within the scope of the controller,
effectively creating dynamic model properties that are used to handle
user input.
What does he mean by
creating dynamic model properties that are used to
handle user input.
?
Do we add a new property to the model named actionText ? . We add a ""pseudo""-property so the controller can use it to grab an input value?
If this is the case, then why use ng-model="actionText" and not just use plain old getElementById? After all we only need the value of the field.
Can something like ng-click getElementById("myInput").value do the trick and also avoid adding unwanted stuff to the model?
Thanks
One of the features of angularjs is to abstract as much as possible the manual javascript manipulation of the DOM.
Imagine having 10 fields on a form and having to do getElementById…..It’s much simpler in angularjs. You just need to have ng-model in each input type and on your controller methods you just use your model. You don't even have to pass them as arguments; the method on the controller will have access to the model.
I think that using ng-model will ultimately generate a cleaner code than using getElementById to access each field value.

Two way binding for angular strap

I'm fairly new to angularjs and am using angular strap to use some of their features like aside.
So basically I have a function like this:
$scope.myAside = function() {
$aside({
title: 'My Title',
show: true,
animation: 'am-slide-left',
placement:'left',
contentTemplate: 'views/aside.html'
});
};
and I wanted to know how to use the two binding to get 'show' to display on the page. I basically wanted something like this on the page:
The aside is open: {{myAside.show}}
I read through their setup but couldn't find anything about two binding and how to know if the aside modal is open or not.
Here is the plugin with the documentation about aside. http://mgcrea.github.io/angular-strap/#/asides
You need to use {{ myAside.$scope.$isShown }}
Here is a plnkr showing how this works.

"Referencing the Window in Angular expressions is disallowed!" occurs when not using window

I recently moved from AngularJS v1.2.17 to v1.2.28 which is the latest stable build and I realized that one of my pages now throws a bunch of JS errors telling me the following:
Error: [$parse:isecwindow] Referencing the Window in Angular
expressions is disallowed! Expression: model.skAccount.Phone
The thing is I have not touched this code in weeks so it makes no sense that it would not work. After looking for some reference to window in the template or my controller I found nothing at all.
This is what the HTML looks like for this Expression:
<li>
<label for="inputAccountPhone">Phone number</label>
<input id="inputAccountPhone" type="text" sk-phone-number ng-model="model.skAccount.Phone" required/>
</li>
This is just a simple form and there is absolutely no reference to window. I then reverted my AngularJS version back to 1.2.17 and everything works just fine. Does this sound like a bug with Angular?
Requested skPhoneNumber directive
app.directive('skPhoneNumber', function(){
return {
replace: false,
restrict: 'A',
scope: {
phone: '=ngModel'
},
link: function(scope){
scope.$watch('phone', function(newVal){
if(newVal){
scope.phone = newVal.replace( /^(\d{3})(\d{3})(\d{4})$/, '($1) $2-$3');
}
});
}
}
});
UPDATE
After further testing, I found that the problem was introduced in v1.2.19
So I am not sure why the error was thrown on this particular expression since I have many expressions preceding this one, but I have found the problem.
In my controller I always use $scope.model for my view model and I typically initialize the model by doing something like
function HomeViewModel(){
this.title = "Hello";
this.content = "World!";
}
$scope.model = new HomeViewModel();
However I accidentally had only
$scope.model = HomeViewModel();
So I forgot my new operator. Leaving out the new operator caused the this reference inside of my HomeViewModel class to become a reference to window. Therefore, instead of having:
$scope.model = {
title: "Hello",
content: "World!"
}
$scope.model was equal to window with title and content extended. Because of this, the newer versions of Angular run a check to make sure the objects are NOT window objects and this caused that check to fail and the exception was thrown.
For some reason, prior to AngularJS v1.2.19, this check was never being done so my code has been just "working".

Access config constants inside view in angularjs

I've configured a constant on a module like below(simplified version of my actual scenario):
var app = angular.module('app', []);
angular.config('AnalyticsConstant', function(){
property: {
click: 'clicked',
swipe: 'swiped',
externalLink: 'opened external link'
},
page: {
message: {
list: 'Message listing',
show: 'Message show'
}
}
}
Now I based on user action taken (say swipe), I want to trigger an analytics event. Since swipe/click or recognizing if an element has an external link, is something done on view level, I want to pass a hash to my controller method.
for example:
<ion-list>
<ion-item ng-repeat="message in messages track by $index" ui-sref="message_path({id: message.id})" class="item-text-wrap">
<my-track-directive source='message', property='click'>
</ion-item>
</ion-list>
Here certainly in myTrackDirective, I can read these two values and check if source/property key is available in AnalyticsConstant. Once found out, I'll also have to check if the value are key another key in AnalyticsConstant.source/property hash. Another pain will be, I'll need to stringify the keys source/property so that I can check in hash, however that's not a problem.
I was wondering if I can access the AnalyticsConstant in view, so that the directive line becomes something like:
<my-track-directive source='AnalyticsConstant[page][message][list]', userAction='AnalyticsConstant[property][click]'>
I could think of three solutions:
Add inject it root scope. ie.
app.run(function($rootScope, AnalyticsConstant){
$rootScope.analyticsConstant = AnalyticsConstant
}
But this is not a good solution, as if by mistake anyone changes $rootScope.analyticsConstant to something else, whole functionality may get screwed.
Inject in each controller and set it at $scope level.
$scope.analyticsConstant = AnalyticsConstant
This will be a lot of duplication. Also, it'll also not ensure if $scope.analyticsConstant won't get corrupted by mistake.(Though disaster will also be scoped and limited :) )
Write a function which returns AnalyticsConstant inside 'module.run' or may be inside each controller.
function getAnalyticsConstant = function(){
return AnalyticsConstant
}
I particularly liked the third approach. But question remains where to place this (rootScope or controller-scope)?
My questions are:
Is there any better way to access configured constant inside view in angular?
What might be other problems with each of these approach I listed above?
Is the directive approach is better or should I collect this data in some model and then pass it to analytics event function ?
Thanks
I would use value to define constants:
app.value('AnalyticsConstant', function(){
property: {
click: 'clicked',
swipe: 'swiped',
externalLink: 'opened external link'
},
page: {
message: {
list: 'Message listing',
show: 'Message show'
}
}
}
So in each controller/directive you just nee to create instance, for example:
app.directive('myTrackDirective', ['AnalyticsConstant', function(AnalyticsConstant) {
return {
restrict: 'E',
replace: true,
scope: {/* ... */},
templateUrl: 'some.html',
link: function(scope) {
scope.AnalyticsConstant = AnalyticsConstant;
}
};
}]);
After you can call AnalyticsConstant from HTML
As a side note (doesn't refer to question):
Try to use MixPanel. Works great for Cordova-Ionic-Angular

What's the generalizable way to get values out of a directive?

I'm writing a little threaded discussion board in angular. I want to use hallo.js for my inline editor (or something similar, the problem doesn't actually depend on hallo).
Here's the relevant snippet from the template
<div ng-show="post.editing" ng-bind-html="post.body" edit-hallo class="col-xs-8"></div>
<div ng-show="!post.editing" ng-bind-html="post.body" class="col-xs-8"></div>
Here's my directive:
Appl.directive('editHallo', function () {
return {
restrict: 'AC',
scope: true,
link: function(scope, element, attr) {
element
.hallo({
plugins: {
'halloformat': {"bold": true, "italic": true, "strikethrough": true, "underline": true},
'halloheadings': [1,2,3],
'hallojustify' : {},
}
});
element.bind('hallomodified', function(event, data) {
scope.post.body = data.content;
});
}
};
});
This all works just fine, but the hack is right there at the end - when there's a hallomodified event, I manually say, scope.post.body = data.content which not only feels like a hack, it means this only works when there's a post.body item that I'm editing, and therefore doesn't work well if I want to repurpose this for the profile editor or whatever.
So my question is: how should I refactor this so that the relevant two-way binding works? I tried a few things that seemed obvious, such as putting a app-model="post.body" in the div, and then doing an isolate scope with =, but that wasn't getting me anywhere. Ideally, I'd pass in the appropriate model using an ng-model directive, but that seems to have changed sometime between when all the directive examples I found online were created and angular 1.2.0.
There's been some time I don't use AngularJS.
But I think the best way would be to change the scope to something like:
scope:{ngModel:'='}
or
scope:{attribute:'='}
That way it should make a two data binding. One with ng-model on first case, or attribute on second.
Then you can just do this when event happens:
scope.$apply(function(){
scope.ngModel=newValue;
})
The apply will be needed so angular can call digest cycle again and update the view.
More info, I think this can help:
http://docs.angularjs.org/guide/directive

Resources