AngularJS - ng-click not working in custom directive - angularjs

I have created a custom directive. The controls defined in templates seem to be working fine. But, I need to append another control - image button, to a control defined in templates according to some condition. To do this I included an IF condition, created an image button and added a 'ng-click' attribute to it. However, using javascript 'append' of image button to my template control's ('html') does not seem to work as it says no 'append' is an undefined function. Hence I used 'concat()', this renders the image button but 'ng-click' does not work. The function in the controller does get hit. Please help!
<div ng-controller="pdfFormsEditController">
<split-pane id="splitPane" height="800">
<split-pane-component id="leftPane" width="50%"> <div class="form-editor boxed-section dashed" id="form">
<form-control ng-repeat="control in formDefinition.Definition.controls" control="control" />
</div></split-pane-component>
</split-pane>
</div>
This is my directive file -
"use strict";
angular.module('configurePDF.directives.noteForms', [])
.directive('formControl', ['$compile', function ($compile) {
var templates = {
"NoteLabel":'<label ng-click="onSelectNoteField(control.Name)" ng-style="control.style" style="font-style:Bold; font-size: 8.25pt; font-family:Arial">{{control.Properties.DisplayName}}</label>',
"NoteMedcinFinding":'<p ng-click="onSelectNoteField(control.Name)" ng-style="control.style"><input type="image" src="../Images/YesNo.gif" style="Width:35px; Height:17px;"><label style="font-style:Bold; font-size: 8.25pt; font-family:Arial">{{control.Properties.OriginalText}}</label></input></p>'
}
var linker = function(scope, element, attrs) {
var location = scope.control.Properties.Location.split(',');
var size=scope.control.Properties.Size.split(',');
// var font=scope.control.Properties.Font.split(',');
scope.control.style = {position: "absolute", left: (parseInt(location[0])*1.5) + "px", top: location[1] + "px" ,minWidth:425+"px",height:size[1]+"px"};
var html = templates[scope.control.Type];
debugger;
if(scope.control.Properties.DetailFormID != "00000000-0000-0000-0000-000000000000"){
var img = document.createElement('input');
img.type = "image"
img.src = "../../Images/plus.png";
img.height = 10;
img.width = 10;
img.style.position = 'absolute'
img.style.left = ((parseInt(location[0])*1.5) - 13) + "px"
img.style.top = (parseInt(location[1]) + 3)+ "px"
//img.className ="ng-scope ng-binding"
//img.onclick = "onSelectNoteField(scope.control.Properties.DetailFormID)"
var attr = document.createAttribute("data-ng-click");
attr.value = "onSelectNoteField(control.Properties.DetailFormID)";
img.attributes.setNamedItem(attr);
debugger;
html = html.concat(img.outerHTML);
//console.log(element);
}
var elem = $compile(html)(scope);
element.replaceWith(elem);
}
return {
restrict: "E",
replace: true,
link: linker,
scope: {
control:'='
}
};
}]);
UPDATE: I read that class="ng-scope ng-binding" should be created automatically in the element's html. But in my image button the class is is just "ng-scope". So, may be there is some binding issue to the scope, because I was not able to append the image button but concatenate it??
<input type="image" src="../../Images/plus.png" height="10" width="10" style="position: absolute; left: 32px; top: 83px;" data-ng-click="onSelectNoteField(control.Properties.DetailFormID)" class="ng-scope">
Controller:
/
.controller('pdfFormsEditController', ['$scope', '$routeParams', 'pdfFormsService','mastersService','$rootScope',
function pdfFormsEditController($scope, $routeParams, pdfFormsService,mastersService,$rootScope) {
var allNoteFields = [];
$scope.editMode = true;
$scope.onSelectNoteField = function(noteField) {
debugger;
$scope.formDefinition = {};
var result = pdfFormsService.getFormDefinition('3151ff0d-6c93-4c80-9182-fd05f7d6cf90');
result.success(function (formDefinition) {
console.log(formDefinition);
$scope.formDefinition = formDefinition;
var size = $scope.formDefinition.Definition.Properties.Size.split(',');
$scope.formDefinition.style = { position: 'relative', width: size[0] + "px", height: size[1] + "px" }
}

Well, I found the answer and its pretty simple! My directive had no access/link to the scope when I was calling onSelectNoteField! Hence a different approach worked.
if ((scope.control.Properties.DetailFormID != "00000000-0000-0000-0000-000000000000") && (scope.control.Properties.DetailFormID != "0") && (scope.control.Properties.DetailFormID !== undefined)) {
var img = document.createElement('input');
img.type = "image";
img.src = "../../Images/plus.png";
img.height = 10;
img.width = 10;
img.style.position = 'absolute'
img.style.left = ((parseInt(location[0]) * 1.5) - 13) + "px"
img.style.top = (parseInt(location[1]) + 3) + "px"
//img.className ="ng-scope ng-binding"
//img.onclick = "onSelectNoteField(scope.control.Properties.DetailFormID)"
var attr = document.createAttribute("data-ng-click");
attr.value = "onImageClick('" + scope.control.Properties.DetailFormID + "')";
img.attributes.setNamedItem(attr);
html = html.concat(img.outerHTML);
}
var elem1 = $compile(html)(scope);
element.replaceWith(elem1);
scope.onImageClick = function (subformID) {
var id = subformID;
scope.onImageClick = function (control) {
var scope = angular.element($("#form")).scope();
scope.onSelectNoteField(id);
}
}
I am creating a scope property- the function onImageClick and calling this in ng-click of my image. onImageClick gets the scope of my parent element, which has access to the function I want to call - onSelectNoteField!

Related

Passing through variable on ng-compile returns undefined

var suggestionElement = document.createElement('div');
vm.suggestActivate = function(keyCode) {
if(keyCode === 32) {
if(vm.summaryData) {
var words = vm.words;
var suggestions = "<div></div>";
var targetElement = event.srcElement;
var targetElementModel = targetElement.getAttribute("ng-model");
for(var i = 0; i < 5; i++) {
$log.debug(targetElementModel);
suggestions += '<div ng-click="vm.appendSuggestion(targetElementModel)" style="padding: 10px; border: 1px solid #B0B0B0;" onMouseOver="this.style.backgroundColor=\'#D8D8D8\'" onMouseLeave="this.style.backgroundColor=\'#F3F3F3\'">' + words[Math.floor(Math.random() * words.length)] + '</div>';
}
suggestionElement.innerHTML = suggestions;
suggestionElement.setAttribute('style', 'background: #F3F3F3; position: relative; top: -3.9em; width: 25%');
suggestionElement.style.display = 'block';
angular.element(targetElement.parentNode).append($compile(suggestionElement)($scope));
}
}
else {
suggestionElement.style.display = 'none';
}
};
vm.appendSuggestion = function(model) {
$log.debug(model);
}
In the above segment of code in the vm.appendSuggestion method, why is model returned as undefined? If I log it during the for loop, it returns a value? I want to return a value in the vm.appendSuggestion method as well. However, I noticed when it is hardcoded, it does not return as undefined.
When you click on the div element to which you have assigned the ngClick directive, Angular tries to find targetElementModel on the $scope and it does not exist there. You cannot pass a value to it like that.
A solution might be to expose a list of target elements to the view and do something like '<div ng-click="vm.appendSuggestion(' + targetElementIndex + ')" .... But this is just an example - you might find a more appropriate solution for your problem.

How can I hide a (ionic) header without hiding the entire content of the templates?

I have 2 headers and I want to hide the 2nd when scrolling trough the content, which is implemented with 'ion-slide-box' and 'ion-slide' to be able to slide the tabs and change from one template to the other (with different controller each template).
.scroll-content{
top: 0 !important;
bottom: 0 !important;
}
1st header implemented in a directive template
<fake-statusbar></fake-statusbar>
2nd header
<ion-content scroll="false" class="bar-positive">
<ion-slide-box slide-tabs-scrollable="false" show-pager="false" ion-slide-tabs>
<ion-slide ion-slide-tab-label="Catalog"><ion-nav-view name="tab-games"></ion-nav-view></ion-slide>
<ion-slide ion-slide-tab-label="Social"><ion-nav-view name="tab-chats"></ion-nav-view></ion-slide>
<ion-slide ion-slide-tab-label="Video"><ion-nav-view name="tab-videos"></ion-nav-view></ion-slide>
</ion-slide-box>
</ion-content>
I am trying to call the 'slidingTabs' class which is by default the class of the 2nd header (is hidden), in a different directive to hide that selected class when scrolling trough the content.
.directive('headerShrink', function ($document, $ionicScrollDelegate) {
var fadeAmt;
var shrink = function (header, content, amt, max) {
amt = Math.min(44, amt);
fadeAmt = 1 - amt / 44;
ionic.requestAnimationFrame(function () {
header.style[ionic.CSS.TRANSFORM] = 'translate3d(0, -' + amt + 'px, 0)';
for (var i = 0, j = header.children.length; i < j; i++) {
header.children[i].style.opacity = fadeAmt;
}
});
}; return {
restrict: 'A',
link: function ($scope, $element, $attr) {
var starty = $scope.$eval($attr.headerShrink) || 0;
var shrinkAmt; var header = $document[0].body.querySelector('div.slidingTabs');var headerHeight = header.offsetHeight; $element.bind('scroll', function (e) {
var scrollTop = null;
if (e.detail) {
scrollTop = e.detail.scrollTop;
} else if (e.target) {
scrollTop = e.target.scrollTop;
}
if (scrollTop > starty) {
// Start shrinking
shrinkAmt = headerHeight - Math.max(0, (starty + headerHeight) - scrollTop);
shrink(header, $element[0], shrinkAmt, headerHeight);
} else {
shrink(header, $element[0], 0, headerHeight);
}
});
}
};
})
and I add in each template content 'header-shrink scroll-event-interval="5"'
All of this works only for the 1st header class (to hide the 1st header) or when I call the class 'bar-positive' which hide the whole content. NOT with' .slidingTabs' class. Can someone have an idea why it doesn't work? Or maybe can someone give me a hint of how to implement it? I am trying to guide myself from these two demos
Shrinking Header: Nightly
and
Facebook Style Shrinking Header & Tabs Ionic beta.13 tabs

How can I load youtube videos with angularjs?

I am trying to delay loading of youtube videos via javascript code as explained here.
and here is the code for that:
function optimizeYouTubeEmbeds() {
// Get all iframes
var frames = document.getElementsByTagName('iframe');
// Loop through each iframe in the page.
for (var i = 0; i < frames.length; i++) {
// Find out youtube embed iframes.
if (frames[i].src && frames[i].src.length > 0 && frames[i].src.match(/http(s)?:\/\/www\.youtube\.com/)) {
// For Youtube iframe, extract src and id.
var src = frames[i].src;
var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
var id = (src.match(p) ? RegExp.$1 : false);
if (id == false) { continue; }
// Get width and height.
var w = frames[i].width;
var h = frames[i].height;
if (src == '' || w == '' || h == '') { continue; }
// Thease are to position the play button centrally.
var pw = Math.ceil(w / 2 - 38.5);
var ph = Math.ceil(h / 2 + 38.5);
// The image+button overlay code.
var code = '<img src="http://i.ytimg.com/vi/' + id + '/hqdefault.jpg" style="" />';
// Replace the iframe with a the image+button code.
var div = document.createElement('div');
div.innerHTML = code;
div = div.firstChild;
frames[i].parentNode.replaceChild(div, frames[i]);
i--;
}
}
}
// Replace preview image of a video with it's iframe.
function LoadYoutubeVidOnPreviewClick(id, w, h) {
var code = '<iframe src="https://www.youtube.com/embed/' + id + '/? autoplay=1&autohide=1&border=0&wmode=opaque&enablejsapi=1" width="' + w + '" height="' + h + '" frameborder=0 allowfullscreen style="border:1px solid #ccc;" ></iframe>';
var iframe = document.createElement('div');
iframe.innerHTML = code;
iframe = iframe.firstChild;
var div = document.getElementById("skipser-youtubevid-" + id);
div.parentNode.replaceChild(iframe, div);
}
This code worked before switching my project to the angular way of doing things.
I initially thought I could just wrap that code in a $scope and call it, but that did not work. Here is my code that retrieves the videos:
$scope.renderHtml = function (html) {
return $sce.trustAsHtml(html);
};
$scope.Videos = [];
$scope.getVideos = function() {
// api call
$http.get('/api/Videos/AllVideos')
.success(function(data) {
$scope.Videos = data;
});
};
$scope.getVideos();
and here is the html tag where it is rendered:
<div ng-repeat="vid in Videos">
<div ng-bind-html="renderHtml(vid.url)"></div>
</div>
The vid.url includes all the iframe tag information etc. What do I need to do to make this work?

height issue with AngularSlideables

I'm using AngularSlideables:
https://github.com/EricWVGG/AngularSlideables
I'm having a problem where the slider isn't working on chrome. If I manually set the height it works, but I have dynamic content so I can't set a fixed value.
Here's my code:
.directive('slideable', function () {
return {
restrict:'C',
compile: function (element, attr) {
// wrap tag
var contents = element.html();
/*
I need to get the height of the contents variable above so that I can set it within element.html, like so:
<div class="slideable_content" style="margin:0 !important; padding:0 !important; height: contents.height" >
*/
// var height = element.html().prop('offsetHeight');
element.html('<div class="slideable_content" style="margin:0 !important; padding:0 !important;" >' + contents + '</div>');
return function postLink(scope, element, attrs) {
// default properties
attrs.duration = (!attrs.duration) ? '1s' : attrs.duration;
attrs.easing = (!attrs.easing) ? 'ease-in-out' : attrs.easing;
element.css({
'overflow': 'hidden',
'height': '0px',
// 'max-height' : '0';
//'height': '1500px',
// 'height': content.scrollHeight,
'transitionProperty': 'height',
'transitionDuration': attrs.duration,
'transitionTimingFunction': attrs.easing
});
};
}
};
})
Any ideas how I can get this working?
The issue with this is not the above code. There is another directive.
For some reason Chrome doesn't like doing this:
var content = target.querySelector('.slideable_content');
target.style.height = content.scrollHeight + 'px';
This worked instead:
var target = document.querySelector(attrs.slideToggle);
target.style.height = target.scrollHeight + 'px';

Moving angularJS elements created by ng-repeat

I'm trying to move elements that are created by a ng-repeat into some columns. I successfully did it with a directive, but the problem happens when I sort the array of objects on which ng-repeat operates. The directive that searches for the smallest column and then insert the element in it fails to determine the smallest column (maybe because there are still elements in the columns).
I believe the structure I use (directives / controllers etc...) isn't optimal, and I cannot find how to organize the angular code to get the behavior I want.
Here is a jsFiddle showing what I have now : http://jsfiddle.net/kytXy/6/ You can see that the items are being inserted correctly inside the columns. If you click on a button that re-arranges the sorting, then they are not inserted again. If you click multiple times on a same button, watch what happens...
I put commented alerts that you can uncomment so that you can see how items are being inserted and what is wrong. I've also tried emptying the columns before inserting again (commented js in the jsfiddle), whithout any success.
Here is the code :
HTML:
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<div ng-app="myModule">
<div ng-controller="Ctrl" >
<button ng-click="predicate = 'id'; reverse=false; setupColumns()">Sort ID</button>
<button ng-click="predicate = 'id'; reverse=true; setupColumns()">Sort ID reversed</button>
<div id="columns" generate-sub-columns post-render>
</div>
<div class="elements">
Elements are stored here !
<div class="element" ng-repeat="(key,elt) in elts | orderBy:predicate:reverse" id="element{{key}}">
Text: {{elt.a}}
</div>
</div>
</div>
</div>
JS:
var myModule = angular.module('myModule', []);
myModule.controller('Ctrl', function($scope) {
$scope.predicate='id';
$scope.reverse=false;
$scope.elts = [
{id:0,a:"Hi man !"},
{id:1,a:"This is some text"},
{id:2,a:"Wanted to say hello."},
{id:3,a:"Hello World!"},
{id:4,a:"I love potatoes :)"},
{id:5,a:"Don't know what to say now. Maybe I'll just put some long text"},
{id:6,a:"Example"},
{id:7,a:"Example2"},
{id:8,a:"Example3"},
{id:9,a:"Example4"},
{id:10,a:"Example5"},
{id:11,a:"Example6"}
];
$scope.setupColumns = function() {
console.log('calling setupColumns');
var eltIndex = 0;
var element = jQuery("#element0");
/*while(element.length > 0) {
jQuery('#elements').append(element);
eltIndex++;
element = jQuery("#element"+eltIndex);
alert(1);
}
alert('Columns should be empty');*/
element = jQuery("#element0");
eltIndex = 0;
var columnCount = 0;
while (jQuery("#column"+columnCount).size() >0)
columnCount++;
while(element.length > 0) {
console.log('placing new element');
var smallestColumn = 0;
var smallestSize = jQuery("#columns").height();
for (var i = 0; i < columnCount; i++) {
var columnSize = jQuery(".column#column"+i).height();
if (columnSize < smallestSize) {
smallestColumn = i;
smallestSize = columnSize;
}
};
jQuery('.column#column'+smallestColumn).append(element);
eltIndex++;
element = jQuery("#element"+eltIndex);
//alert(1);
}
//alert('Columns should be filled');
};
});
myModule.directive('generateSubColumns', function() {
return {
restrict: 'A',
controller: function() {
var availableWidth = jQuery("#columns").width();
var sizePerColumn = 100;
var nbColumns = Math.floor(availableWidth/sizePerColumn);
if (nbColumns<=1)
nbColumns=1;
for (var i = 0; i < nbColumns; i++) {
jQuery('<div class="column" id="column'+i+'">Column '+i+'</div>').appendTo('#columns');
};
}
};
});
myModule.directive('postRender', [ '$timeout', function($timeout) {
var def = {
restrict: 'A',
terminal: true,
transclude: true,
link: function(scope, element, attrs) {
$timeout(scope.setupColumns, 0);
}
};
return def;
}]);
and some css:
#columns {
width: 100%;
}
.column {
width: 100px;
display:inline-block;
vertical-align: top;
border: 1px solid black;
}
.element {
border: 1px solid red;
}
How can I fix that ?
Thanks in advance,
hilnius
First.. Why you are doing something like this?
var element = jQuery("#element0");
Inside a controller?
That kind of code (DOM manipulation) should go inside link function directive and use the $element parameter to access to DOM element.
Also.. What if you use the column-count property to divide your container? https://developer.mozilla.org/es/docs/Web/CSS/column-count

Resources