angularjs directive cannot get attributes ending with "-start" - angularjs

I wanted to use on-drag-start as an attribute in AngularJS directive, called ngDraggable.
However, it seems not possible to have that attribute.
The following code is in pure javascript, and I can get on-drag-start as an attribute.
I think I can get any attribute regardless of attribute name.
<h1 id="tag1" on-drag-start="START" on-drag="DRAG" on-drag-end="END" >Hello Plunker!</h1>
Attributes in pure javasctipt DEMO: http://plnkr.co/edit/6iODSnf56KtwPFpoC7ck?p=preview
However, in the following code in AngularjS, I cannot get onDragStart as an attribute, but it is possible to get onDragBegin as an attribute.
<h1 id="tag1" ng-draggable on-drag="DRAG" on-drag-end="END"
on-drag-start="START" on-drag-begin="BEGIN">Hello Plunker!</h1>
Attributes in AngularJS directive DEMO: http://plnkr.co/edit/RxABAHHlxQJSSZz91CYW?p=preview
Of course, I can change my attribute name from on-drag-start to on-drag-begin, but I am curious.
My questions are;
why I cannot use on-drag-start as an attribute name?
what's the reason behind it?
and, is there any general rule for attribute names?
NOTE: I feel my question is not well formatted. Rewriting is welcomed.

This was broken in 1.2. It's best to rename the attributes to something else and move on.

I just ran into this exact thing today and I wasted an hour or so trying to figure out what was wrong with my directive. I am implementing a touch event directive and I named it dac-touch-start, and was baffled why it did not fire. Eventually I figured out that the word "start" was to blame.
Looking through the Angular source, it seems there is a special provision for the words "start" and "end" in directive names.
This is the function that links the directives in your templates to the directive definition.
/**
* Looks for directives on the given node and adds them to the directive collection which is
* sorted.
*
* #param node Node to search.
* #param directives An array to which the directives are added to. This array is sorted before
* the function returns.
* #param attrs The shared attrs object which is used to populate the normalized attributes.
* #param {number=} maxPriority Max directive priority.
*/
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
...
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
if (ngAttrName === directiveNName + 'Start') {
attrStartName = name;
attrEndName = name.substr(0, name.length - 5) + 'end';
name = name.substr(0, name.length - 6);
}
This appears to be so that directives such as ng-repeat-start ... ng-repeat-end can be supported.
As #mmattax suggests, the thing to do is to just name it something else.

So, coughcough it appears that appending a frivolous (additional) -start is a workaround.
i.e. on-drag-start-start="onStartCallback()".
results in an attribute of on-drag-start in the markup,
and it appears to work, on angular 1.2.10.
Your mileage may vary, and I haven't checked for any other misbehavior.

Related

Using contenteditable with ng-model inside ng-repeat?

Here is my issue:
I am using ng-repeat to make a list of spans.
Each span has the contenteditable attribute and ng-model directive.
Everything works as expected (including two-way data binding), until I try to add a new item to the list.
<div ng-repeat="item in list">
<span ng-model="item.text" contenteditable></span>
</div>
<button ng-click="addItemToList"></button>
The methods look like this:
$scope.addItemToList = function () {
$scope.list.push({text: 'dummy text'});
}
or
$scope.addItemToList = function () {
$scope.list.splice(1, 0, {text: 'dummy text'});
}
When adding the new item to the list (push or splice), the DOM updates, but the last item is initialised empty, with no text whatsoever. The last item in the model list also empties out, even if I specifically push an element with text in it.
After a few tests, I've noticed that this only happens if the list's length is bigger after modifying it:
if I try to replace/modify/remove (not add) an element in the list, it works well.
I believe this has to do with the way contenteditable elements initialise in the DOM (I think they initialise empty, and somehow, the model empties out as well).
Has anyone encountered this problem before? If yes, how did you solve it / what workaround have you found?
Based on the angular docs related to ngModelController, it seems that there is not built-in support for two-way data binding with contenteditable elements, seeing as in the plunkr example they wrote their own contenteditable directive. You might be able to use a modified version of that as a workaround.
It looks to be a similar problem as this question and the contenteditable directive there looks similar to the contenteditable directive in the angular docs example.
I also found this directive on github that might accomplish what you are trying to do.
Edit: I did a new version of the plunk I posted in the comment above:
https://plnkr.co/edit/v3elswolP9AgWHDIPwCk
In this version I added a contenteditable directive that appears to be working correctly. It is basically a spin off of how the input[type=text] directive is written in angular, but I took out the places where it handles different types of input (since in this case it will just be text) and the places where it handles events that contenteditable elements don't even fire. I also changed it so that the $viewValue gets updated based on element.html() instead of element.val(). You might be able to use something like this as a workaround
The issue is old but that was the same problem for me today. (angular 1.5). My workaround was to add on blur update option: <td contenteditable data-ng-model="position.value" ng-model-options="{updateOn: 'blur'}"></td> somehow then model stopped getting be empty on initialize. I like using update on blur in many places (solves some performaces issues) so this is good for me, however it's some kind of trick.

ng-html-bind and syntax highlighting

I have an element with ng-html-bind that loads HTML content:
<p ng-bind-html="content.body"></p>
Inside this content I have one or more <code> blocks.
I would like to apply syntax highlighting only to the code tags of the loaded content, for example using angular-highlightjs directive.
Any idea on how to achieve this?
There are many ways to bind html on the page with Angular. One way is with ng-bind-html, but it's not really the best for this use case, since you also need angular-highlightjs directive to compile. You can achieve your goal with $compile like this:
/** Here, you will need to do some transformations to your html string
* 1. Add `hljs` attribute or `class="hljs"` to the `<code>` tag in any `<pre><code>`
* 2. Hopefully you already have your line breaks in place. This will result in
* a single line code block otherwise. See my plunk for how I added '\n'
*/
var myHTML = $scope.content.body;
element.append( $compile( myHTML )($scope) );
See my plunk

Seemingly unable to access content generated in the link() function while attempting to unit test an angular directive

I am working on an enterprise project, and as a part of that project we have a directive for creating form fields with appropriate document markup and validation/error handling. As an example, consider something like the following:
<form name="myForm">
<my-form-field label="My Field">
<input type="text" name="myField" id="myField" ng-model="myField" ng-required="true" />
</my-form-field>
</form>
The directive works exactly as expected, properly surrounding my input field with the common/expected markup, adding appropriate event handlers, etc.
One key piece of functionality is a convenience feature where the directive "notices" that the child form field has an attribute value for ng-required of true (either directly, as in this example, or because it is bound to something which is evaluated). This attribute does the "normal" angular thing (hooking into field-level validation in the angular workflow), but then my directive also notices it's presence in the child element, and appends " *" to the label value when taking the label attribute of the directive element and creating an HTML as part of the markup which is generated.
When attempting to unit test this in jasmine/karma, I see the logic in the template() portion of my directive firing (e.g., the tag is generated), but not the items in the link() portion (e.g., adding the asterisk to the label text, updating the for="" attribute of the label tag to match the id="" attribute of the child tag).
After spending the morning researching various demos/tutorials on unit testing angular directives, I found an example which didn't help me much...but did offer a smaller/more basic example of what I am seeing: http://plnkr.co/edit/tirhLwFEXLKSzukbsW1q?p=preview
In this example, seeing the attribute values set by the link() function of the direction is easily handled (see the expectations at lines 23-25), but the rendered html still doesn't seem to be compiled (outputting element.html() anywhere in that it() still shows the angular variables - like {{values.center}} - instead of the compiled values). I recognize that I could put computed values on the scope and then follow this pattern of validating by checking the directive's scope...but in my case, I am further modifying the html within the link() portion (quite successfully actually, despite what the unit tests appear to be showing).
What am I missing in terms of being able to "see"/validate the compiled HTML (e.g., validate that an asterisk was added to the label text when appropriate)? I thought about doing all of my markup work in the template() portion of my directive, but then I can't see how I could inspect the child input element (to get the id for the for="" attribute of the label, and to check if it is required or not so that I know if the asterisk is appropriate for the label text).
EDIT:
- This plunkr is most advanced of my three scenarios, and fails (basically a copy of my code, including trying to combine all the angular template generation/compilation into one common function, based on an example I had seen in a few different places): http://plnkr.co/edit/AxGAUKYFvm2yf1vPsWD3?p=preview
In the third plunkr (see my comment below for the first two, each of which works as expected), the error thrown is actually "TypeError: currentSpec.queue is undefined...", which is likely my culprit - but I can't seem to find a way around it. I actually wasn't seeing this error locally...because I had seen some advice in a few places to change this method within angular-mocks.js:
isSpecRunning = function() {
//return currentSpec && (window.mocha || currentSpec.queue.running);
return !!currentSpec;
};
The second line is the original, which was commented out/replaced with the third line. This removes the error shown in the Plunk, and instead my unit test runs just fine...except for it doesn't seem to show the final compiled version (e.g. the label's for attribute is never populated, and the asterisk is not added to the label text when appropriate). So with the suggested change, my test runs ok - but fails to see the
"final" markup which I see when I actually run my site. Without the change to angular-mock.js, I see the error you see in the plunkr.

Evaluating moustache expressions after the page was initialized (dynamic binding)

I have a HTML-Document containing moustache expressions that angular-dart evaluates very well:
</head>
<body ng-cloak>
<ctrlTextElements>
<div id="stage">outside: {{ctrlTextElements.test1('three')}}</div>
</ctrlTextElements>
I want to dynamicaly add some HTML with moustache expression like so:
CtrlTextElements.addTextElement(mousePos.x, mousePos.y);
var div = dom.querySelector('#stage');
HttpRequest.getString("../path/text.html").then((r) {
div.children.add(new Element.html(r, validator: new AllowAllValidator()));
});
The content of the added text.html looks like this:
<div>inside: (not evaluated): {{ctrlTextElements.test1('three')}}</div>
That's the result in the browser:
outside: three
inside: (not evaluated):{{ctrlTextElements.test1('three')}}
How can I reevaluate the moustache expressions inside content that has been applied after the page was loaded?
The problem is that you are mixing jQuery like logic with angular logic here : manipulating the dom 'by hand' is rarely a good solution.
The problem here is that your newly added binding has not been compiled by angularjs = it has not been indexed as a directive that should be watched for and updated when scope changes.
Either you try a more angular way, for example using ng-hide or ng-repeat directive to display your content according to the controllers $scope (or another custom directive), or you try to $compile your newly added directive ( but this is bad ) : https://docs.angularjs.org/api/ng/service/$compile .
Maybe try in your controller :
$scope.$compile( div );
Not sure of the syntax though. Maybe you would need to write
<span ng-bind="..."></span>
instead of
{{ ... }}
to make it work.
#Alexhv is right. Sorry for my previous answer. I assumed it is about Polymer. Was already time for bed.
You can find a code example in my answer to this question: setInnerHtml doesn't evaluate Mustache
The pub package bwu_angular (http://pub.dartlang.org/packages/bwu_angular) contains this code as a Decorator (Directive) named bwu-safe-html

Stop AngularJS inserting <span class="ng-scope"></span> using ng-include

I'm using the Foundation layout framework, which automatically floats the last sibling of .column to the right and I really appreciate this is a behaviour. However, AngularJS takes it upon itself to insert span.ng-scope after every div.column, which somehow causes browsers to consider the last span the last sibling of .column (even though it is not).
Specifically the css in Foundation responsible for this is:
[class*="column"] + [class*="column"]:last-child { float: right; }
As I understand it, [attribute*="substring"] should select only siblings that match, so, for the above, only elements whose class attribute contains column (including columns). I would think a span tag whose class attribute that does not contain column should not match (and thus be ignored by :last-child). However, this does not seem to be the case.
Regardless, the span is causing the problem:
Angular buggering it up (jsfiddle)
Works fine without Angular (same jsfiddle, no ng-include)
Is there a way to configure angular to stop inserting those span tags? I would, begrudgingly, modify the css selector to somehow ignore all span tags; however I might eventually need/want to use a span tag.
Since you indicated the div can be moved inside, this works:
<ng-include src="'main.tmpl'"></ng-include>
Then in your template:
<div class="row">
<article id="sidepanels" class="four columns">
...
</div>
I'm not aware of any way to prevent angular from inserting the span tags (I think it keeps track of scopes that way -- for garbage collection).
Also you can try my version of include directive that does not creates a scope: Gist source.
As no scopes are created, AngularJS should not create additional element to mainain scope (it actually use data attributes to store link to scope).

Resources