Error appending Pagedown in AngularJS Directive - angularjs

Based on this question, though I felt this warranted its own question: Google pagedown AngularJS directive
Following the example from this question, it seems to work, however I am running into issues when I try to append a directive to the page.
Here is the code I have in the linking function:
scope.editor_id = null;
if(attrs.id == null) {
scope.editor_id = nextID++;
} else {
scope.editor_id = attrs.id;
}
//append editor HTML
//has to be done here so that it is available to the editor when it is run below
var editor_html = $compile(
'<div id="wmd-button-bar-' + scope.editor_id + '"></div>' +
'<textarea class="wmd-input" id="wmd-input-' + scope.editor_id + '" ng-model="content"></textarea>'
)(scope);
element.find('.wmd-panel').append(editor_html);
var editor = new Markdown.Editor(editor_converter, "-" + scope.editor_id);
editor.run();
However, when I append one of these to the document, I get the following error:
TypeError: Cannot read property 'attachEvent' of null
This error tends to crop up when the wmd-input is not present in the HTML. however, I am adding it with the $compile function, and it works on page load, but not when it is appended. What am I doing wrong here?

UPDATE
I was able to reproduce your problem: http://plnkr.co/edit/R2eDKBreHmYBjPtU0JxD?p=preview
Why typeError: Cannot read property 'attachEvent' of null?
I was wrong with my previous assumption ( the composite linking function do returns the element)
The problem is with the way you use angular.element#find.
angular.element#find only search for child elements not on the whole document.
the DOM element with a .wmd-panel class is not a child of the current element.
This should work fine:
angular.element('.wmd-panel').append(editor_html);

Try doing compile like this:
$compile('template stuff goes here')(scope, function(cloned, scope){
element.append(cloned);
});
You may also have to define your editor inside the callback function because I'm not sure if it's asynchronous or not. You may also want to re-consider having your directive compile and append to itself like this. Why not just add more instances of the entire directive using something like ng-repeat?
Also, if you have multiple instances inside this one directive, you will lose reference to editor. Not sure what's going on outside this code so I can't really tell.

Related

view fails to update after executing ngModelController.$render function

Refer to this code:
http://plnkr.co/edit/4QpPZZib6qGjhx85Do0M
Here's how to replicate the error:
1. Run the plnkr
2. then click on any of the buttons "200", "300" etc. You will notice that the model updates just fine. No issue so far
3. Now paste something in the input box. The paste should work just fine.
4. Now try clicking on any of the buttons.
ERROR:
You will notice that the values in the input box does not updates to model value.
From what I can understand the issue is with my $render function.. however I can't seem to find a fix for it.
scope.handlePaste = function(e) {
var pastedText = e.clipboardData.getData('text/plain');
ngModelController.$setViewValue(pastedText);
ngModelController.$render = function() {
element.html($sce.getTrustedHtml(ngModelController.$viewValue));
};
return false; //prevent the default handler from running
};
}
You're using ngSanitize but you forgot to include it in the plunker. (lib + injection in app and $sce in directive).
Then element is an angular element and doesn't have an html() function.
You can get the raw html element with element[0] which has a property value.
http://plnkr.co/edit/HSOnscaprbyvUwjr0P2l?p=preview

How to render directive template dynamically?

Suppose I have a table such that it adds rows when user click a button. The row adding code looks like this:
success: function (data, textStatus, jqXHR) {
$row = $('a#temp').parents('table').find('tr[id$=' + m + ']');
var $new_rows = $(data['payload'].join("\n"));
$row.after($new_rows);
}
Whereas the success is a non-angular callback function such that the ajax response has came back.
The payload will contain the HTML code along with the controller and directive inside the HTML.
I saw that the row is added correctly but I don't see the newly added row controller and directive get initialize.
Any suggestion is welcome.
Thanks
Even though this is a very bad idea, as #tymeV mentioned in their comment, you should be able to do what you want using the $compile service.
Just inject $compile into whatever function contains the AJAX call and (so long as you also have the $scope available), you can change your success handler to:
success: function (data, textStatus, jqXHR) {
$row = $('a#temp').parents('table').find('tr[id$=' + m + ']');
var $new_rows = $(data['payload'].join("\n"));
$row.after($new_rows);
$compile($new_rows)($scope);
// if this is definitely the success handler for a jQuery.ajax() request or similar
// then do the following. Otherwise, remove it.
$scope.$digest();
}
This should be a last resort though. You really need to try and abandon relying on jQuery, especially to do DOM manipulation, and use the Angular directives instead. It will be much easier for you and anyone else who has to look at and maintain your code to understand.
Remember the old adage, "Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability." (see this question for who said this). The violent psychopath who knows Angular will not think fondly of you for committing this cardinal sin in terms of AngularJS development.

Protractor - select next sibling of the current element

In my code there a list of elements (tr) selected with "protractor.By.repeater".
This list is looped using "forEach".
Within this loop, the element is clicked, and this click should trigger the inclusion of a new "tr" just after the clicked element.
I want to select that new line.
I used :
var nextRow = tr.$(protractor.By.xpath('following-sibling::tr'));
but then with :
nextRow.isDisplayed(function(row){
console.log('row', row);
});
it generates errors such as : " UnknownError: java.util.HashMap cannot be cast to java.lang.String"
Is there another way to achieve what I want, i.e to select the next sibling of the current element ?
Or did I wrote something incorrect there ?
Thanks for any help!
Looks like you are missing a .then there and misinterpreted what isDisplayed does.
Code should be more like:
nextRow.isDisplayed().then(function(visible) {
console.log('is displayed? ', visible);
});
Also your ElementFinder nextRow doesn't look quite alright, what is variable tr below? and Protractor $ is an alias for element(by.css which takes a string argument, not a webdriver.Locator like you interpreted below, in your code:
var nextRow = tr.$(protractor.By.xpath('following-sibling::tr'));
So instead maybe you meant:
var tr = $('table tr.first'); // just guessing here since you didn't provide that code
var nextRow = tr.element(By.xpath('following-sibling::tr'));
Using Xpath selectors is not a better choice as it slows down the element finding mechanism.
I have designed a plugin to address this specific issues: protractor-css-booster
The plugin provides some handly locators to find out siblings in a better way and most importantly with CSS selector.
using this plugin, you can directly use:
var nextRow = element(by.css('table tr.first')).element(By.followingSibling('tr'));
Hope, it will help you...

Replacing static text content inside ngrepeat with ngclick directive in AngularJS

I've tried so many different things here with no luck. Basically i have an array of strings (just sentences). I need to ngRepeat over those and output them. No problem. But I need to be able to do a find and replace for a particular word combination ("social security" in this case), wrap it inside a link and add an ngClick directive to that link.
I'm pretty certain that i need to use a directive and this has something to do with compiling and linking. But Thats about as far as i can get with this. Any help would be most appreciated.
I think you could use something like this : http://jsfiddle.net/DotDotDot/Z9CHL/2/
I'm not sure it's the best implementation, but at least it works
The HTML code is quite simple, I send the custom directive one parameter, the full sentence
<ul ng-repeat='sentence in list'>
<li><span the-dir txt="sentence"></span></li>
</ul>
On the javascript side, it's quite easy but longer :
.directive('theDir', function($compile){
var r=/(social security)/ig;
return {
scope:{txt:'='},
link:function(scope,element,attributes){
scope.aFunction=function(){
console.log('In the directive, you clicked on social security');
}
if(r.test(scope.txt)) {
splitted=scope.txt.split(r);
console.log(splitted)
var newSpan=new angular.element('<span>');
for(var i=0;i<splitted.length;i++)
{
if(r.test(splitted[i])){
var newLink=new angular.element('<a>');
newLink.attr('ng-click','aFunction()');
newLink.addClass('social-security');
newLink.html(splitted[i]);
newSpan.append(newLink);
}
else
newSpan.append(splitted[i]);
}
element.append(newSpan);
$compile(newSpan)(scope);
}
else
{
element.html(scope.txt);
}
}
}
});
I use a Regular Expression to find all occurrences of your sentence 'social security', split the full sentence on these occurences, then replace each time the words by a new angular.element (an HTML link in this case, with a ng-click attribute). I append all the regular words and the links to the original element, and then $compile the newly created span with the working links in order to have a working ng-click.
And it seems to work
I let you dig in the code, and I hope it helps you
Have fun

AngularJS: Accessing $scope objects in e2e tests

I am building a Math tutoring application and would like to test my UI using angular's e2e testing suite.
Currently I am working on a Fraction page that generates a random fraction, displays a series of shaded and unshaded boxes and asks the user to input the fraction formed by the shading.
Using an e2e test, I would like to test how the UI responds to both correct and incorrect input; however, since the fraction is randomized on page load, I do not know what 'correct' input is from inside the test.
The easiest way for me to get the correct answers to input would be to gain access to the Fraction object, located at $scope.problemObject for the controller, and call its API functions .getNumerator() and .getDenominator(). However, I have not found a way to get to this object from within my tests.
Relevant lines from my controller are:
$scope.problemObject = Fraction.random();
// This produces an object with two relevant
// functions (getNumerator() & getDenominator())
What I Have Tried
binding()
Initially I thought binding() would do what I needed, however all calls to binding('problemObject') or binding('problemObject.getNumerator()' and the like issue an error saying that the binding cannot be found. I suspect that this is because $scope.problemObject and the return value of $scope.problemObject.getNumerator() are not directly bound to the UI.
angular.element().scope()
Executing angular.element('#problem').scope().problemObject from the console on the page that I am testing works perfectly; however, trying the same line from within my test issues the following error: 'selectors not implemented'.
I have also tried a few variations:
element('#problem').scope().problemObject: Error: 'Object # has no method 'scope''
angular.element(element('#problem')).scope().problemObject: Error: 'Cannot read property 'problemObject' of undefined'
I guess 'element' in e2e test and 'angular.element' are different objects.
You may want to try reading the value from the view.
if it is input field.
var value = element('#problem').val();
Otherwise, something like:
var value = element('#problem').text();
(Looking into scope object from e2e is kind of cheating in my opinion.)
Edit
I totally misunderstood the question and construct of the web page.
Sorry for the confusion.
What it has to validate is the input fields against numbers of the shaded and non-shaded boxes ('td' elems in this example).
var total = element('td').count()
, fraction = element('td.shaded').count();
Idea is same, it is trying to get the numbers from the view, not from $scope.
Turns out the problem lies in the scope being stored in jQuery's data. Since jQuery stores the data in a hashtable as $.cache global, once we are outside of the frame that the test webpage is running in, we no longer have access to them. The way I solved it is by accessing the jQuery inside the iframe's window (conveniently given in the $window parameter).
Below is what I come up with to access the scope. You can do scope('#myElement', 'foo.bar') to query $scope.foo.bar.
angular.scenario.dsl('scope', function() {
return function(selector, entry) {
return this.addFutureAction('find scope variable for \'' + selector + '\'',
function($window, $document, done) {
var $ = $window.$; // jQuery inside the iframe
var elem = $(selector);
if (!elem.length) {
return done('No element matched \'' + selector + '\'.');
}
var entries = entry.split('.');
var prop = elem.scope();
for (var i in entries) {
prop = prop[entries[i]];
}
done(null, prop);
});
};
});

Resources