Struggling with my first 'in-depth' directive: an image/video gallery - angularjs

I'm trying to build a relatively simple gallery as a directive so I can use it on a couple different templates on my site. Here' what I have so far. The item attr accepts a json object which will contain an 'images' array of objects, each with a url and a label. It also has an optional video property containing a youtube video id. I have a separate youtube embed directive already working that this gallery can use. The $scope.gallery object is intended to contain information about the current state of the gallery as well as functions to modify it. I'm having trouble accessing the item object from within my gallery object. I'm not sure that I'm using the linking function correctly.
app.directive('gallery', function () {
return {
restrict: 'E',
replace: true,
scope: {item: '='},
templateUrl: './app/directives/templates/gallery.html',
link: function ($scope, elem, attrs) {
// this works
console.log($scope);
/* this logs 'undefined' even though the
previous log shows a non empty item property */
console.log($scope.item);
$scope.gallery = {
currentImage: 0,
video: false,
/* I need to set count equal to the number
of images in a given item, but again can't access
$scope.item to see the images property */
count: 5,
next: function () {
if (this.video === true) {
if (this.currentImage < $scope.item.images.length) {
this.currentImage += 1;
}
} else {
if (this.currentImage < $scope.item.images.length -1) {
this.currentImage += 1;
}
}
},
previous: function () {
if (this.currentImage > 0) {
this.currentImage -= 1;
}
},
select: function (index) {
this.currentImage = index;
}
};
}
};
});

I assume that you use some Developer Tools like that of Chrome, right?
In the devtools, if you console.log() an object which is not a primitive value, it will be a dynamic presentation, that means the inner fields will be evaluated only when you read it. Instead, for a primitive value, including undefined, it will be printed as string immediately. When your code was run, $scope.item may have not got the object (sometimes the values are requested remotely right?). But that happened very soon after that. So this is why you can see item in $scope but $scope.item is undefined.
I would suggest that you change all this reference in your functions to $scope.gallery .

Related

AngularJS directive using a service as its scope

I have a simple view problem that I may have made more difficult by trying to create my first directive in Angular. I have this service that returns an array of user roles for the current user. Many users are only going to have the user role. With that in mind I don't want to display anything on their profile page. However, some people like myself will have User and Admin roles. So on the profile page I would like to display a drop down to change the current role.
I thought I'd create a directive that did a check basically
if user.roles.length != 1 then show drop down else remove the element from the dom.
Like I said before I have never created a directive before and they seem like the right way to go but I am not getting the result I would like.
I have this:
app.directive('selector', function (SessionState, $compile) {
return {
priority: 100000,
scope: false,
compile: function (element, attr, linker) {
var data = SessionState.User.Data.roles;
var multiRoleView = '<p>Active Role:</p><select ng-model="State.User.activeRole" ng-options="role for role in data"></select>';
if (data.length != 1) {
element.html(multiRoleView).show();
$compile(element.contents());
} else {
element.children.remove();
element.remove();
}
return function linkFn() {
/* Optional */
}
}
}
});
This will render the html correctly depending on if that user should see it, but for admins it doesn't display any roles because I am assuming my data variable is never being used.
I hate to post a bad question but with some fiddling around and reading the documentation for $compile I found that by setting the scope variable to true. I could simply use the parents scope and that made everything very easy.
The completed and correct code:
Live link: https://app.baileysproject.com
Set scope to true, uses parents scope.
Removed the element.children.remove(), throws errors.
app.directive('selector', function (SessionState, $compile) {
return {
priority: 100000,
scope: true,
compile: function (element, attr, linker) {
var multiRoleView = '<p>Active Role:</p><select ng-model="State.User.activeRole" ng-options="role for role in State.User.Data.roles"></select>';
if (data.length != 1) {
element.html(multiRoleView).show();
$compile(element.contents());
} else {
element.remove();
}
return function linkFn() {
/* Optional */
}
}
}
});

can not access outer once i use mdDiloag in angular meterial

i'm trying to open dialog box using angular material mdDialog with one text box and drop down field but once it open it can not access outer scope data.
my code is
$scope.ouetButton=function(){
$mdDialog.show({
scope: $scope.$new(),
templateUrl: "printDialog.html"
});
// Button click in templete
$scope.closeDialog = function(sheetout, reason, teststartTime) {
$mdDialog.hide();
var sheets = $scope.sheets;
console.log("i got this value undifined ",sheets );
}
}
if i use some paramater like
$scope.ouetButton=function(){
$mdDialog.show({
scope: this,
templateUrl: "printDialog.html"
});
// Button click in templete
$scope.closeDialog = function(sheetout, reason, teststartTime) {
$mdDialog.hide();
var sheets = $scope.sheets;
console.log("i got this value fine",sheets );
}
}
than it get value of sheets but after close it i can not click again on
$scope.ouetButton
To get a JSON string (e.g., from reading a file) into an actual JavaScript object, use JSON.parse(string). Here is an example:
fs.readFile('data.txt', function(err, data) {
if (err) throw err;
var array = JSON.parse(data); // converts the JSON string into an actual object/array
// use array as you did in your question...
});
BONUS: Use a for...of loop to process each element of the array:
for (var obj of array) {
console.log(obj.date_created);
// etc.
}
Note that this is only available since ECMAScript 6, but since you're using Node.js, that shouldn't be a problem.
This file contains a VALID JSON STRING, so just read the file and do JSON.parse(), and then you can do anything else with the JSON object.

Text Placeholders in CKEDITOR (angular context)

I am not very familiar with the CKEDITOR API yet and now I got stuck trying to find the way to create placeholders inside of the CKEDITOR editable area.The expected behaviour for the placeholder - to dissappear on user interaction with it, allowing to edit the content instead.
I know that there is already a placeholder plugin (http://ckeditor.com/addon/placeholder) but its behaviour is not what I am looking for.
To be more specific, the question is: is it possible to subscribe for some events on the particular element inside of the CKEDITOR?
Working in the angular context I am able to compile my html before it is passed to the CKEDITOR ng-model
$scope.html = $compile('<div><span text-placeholder >Placeholder</span></div>')($scope).html();
But then I fail trying to set click events inside of the directive:
.directive('textPlaceholder', [ function () {
return {
restrict: 'A',
link: function ($scope, $element) {
//THIS DOES NOT WORK UNFORTUNATELY
$element.on('click', function () {
console.log('clicked');
})
}
}
}])
Any thoughts?
UPDATE: For now I came up with the solution to implement simple plugin and then reference it in the CKEDITOR config:
(function () {
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('key', function (evt) {
var el = $(CKEDITOR.instances.editor1.getSelection().getNative().baseNode.parentElement);
if (el.hasClass('text-placeholder')) {
el.remove();
}
});
}
});
})();
Looks ugly for me. Any feedback is appreciated.
This seems to be a final Solution:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
editor.on('contentDom', function () {
var editable = editor.editable();
editable.attachListener(editable, 'click', function (event) {
var $placeholder = $(event.data.$.target).closest('.text-placeholder');
if ($placeholder.length > 0) {
var selection = editor.getSelection();
selection.selectElement(selection.getStartElement());
}
});
});
}
});
This applies the selection on the element with "text-placeholder" class when user focuses it inside of the editable area
Update:
See example
You inspired me to write one myself, using the above example as a starting point. In my use case I wanted to take placeholder text from an attribute on the editor -- data-placeholder -- and display it in the editor. When the editor gets focus, the placeholder text disappears. When the editor blurs -- if no user content has been entered -- the placeholder text is displayed again. Additionally, I set a data-placeholder-showing attribute so that I can, for example, use CSS to make the placeholder text gray. Here's my code:
CKEDITOR.plugins.add('text-placeholder', {
init: function (editor) {
var placeholder = editor.element.getAttribute('data-placeholder');
editor.on('contentDom', function () {
if (placeholder) {
editor.setData(placeholder);
editor.element.setAttribute('data-placeholder-showing', true);
}
});
editor.on('focus', function() {
if (editor.getData() === placeholder) {
editor.element.setAttribute('data-placeholder-showing', false);
editor.setData('');
}
});
editor.on('blur', function() {
if (placeholder && editor.getData().length === 0) {
editor.element.setAttribute('data-placeholder-showing', true);
editor.setData(placeholder);
}
});
}
});

Angular $scope losing child data in certain situation

I'm having an issue where, when I simply console.log($scope.visitors) my data is fine, but when I console.log($scope.visitors) and then try to open a AngularUI Bootstrap dialog, the $scope.visitors.optionalFields object is empty.
Now, I've spent some time trying to replicate the issue in jsbin and I wasn't able to get it to happen, so it may be difficult to answer; just hoping somebody might have an idea what could be causing it.
So I have an array of objects like this:
$scope.visitors = [
{
company:"one",
optionalFields: {
passportNumber:"ppt",
contactNumber:"tel",
licensePlate:"1234"
},
firstName:"some",
lastName:"guy",
}
]
I ng-repeat through these with visitor in visitors and each one has a button with ng-click="editVisitorDialog(visitor)"
Now, if I make editVisitorDialog like this:
$scope.editVisitorDialog = function (visitor) {
console.log($scope.visitors);
console.log(visitor);
}
Then both $scope.visitors and visitor look good, they have all of their properties.
However, if I simply add to this function (i.e., the log calls are still at the top of the function), each visitor in $scope.visitors will have an empty optionalFields object.
$scope.editVisitorDialog = function (visitor) {
console.log($scope.visitors); // missing the optionalFields items
console.log(visitor); // missing the optionalFields items
var modalInstance = $modal.open({
templateUrl: 'app/checkin/edit_visitor/edit_visitor.html',
controller: EditVisitorController,
resolve: {
visitor: function () {
return visitor;
}
}
});
modalInstance.result.then(function (result) {
console.log(result)
}, function () {
});
}
When you call console.log() with an object it doesn't really log the object. It's rather like an inspection tool where you can see the current state, not the state at the time console.log() was called.
Try console.log(visitor.optionalFields.passportNumber) instead and I guarantee that it will log "ppt" (in your example).
That in turn means that somehwere in the added code the optional fields get lost.

Access Element Style from Angular directive

I'm sure this is going to be a "dont do that!" but I am trying to display the style on an angular element.
<div ng-repeat="x in ['blue', 'green']" class="{{x}}">
<h3 insert-style>{{theStyle['background-color']}}</h3>
</div>
Result would be
<div class='blue'><h3>blue(psudeo code hex code)</h3></div>
<div class='green'><h3>green(psudeo code hex code)</h3></div>
I basically need to get the style attributes and display them.
Directive Code...
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0], null);
}
}
}];
Fiddle example: http://jsfiddle.net/ncapito/G33PE/
My final solution (using a single prop didn't work, but when I use the whole obj it works fine)...
Markup
<div insert-style class="box blue">
<h4 > {{ theStyle['color'] | toHex}} </h4>
</div>
Directive
directives.insertStyle = [ "$window", function($window){
return {
link: function(scope, element, attrs) {
var elementStyleMap = $window.getComputedStyle(element[0], null);
scope.theStyle = elementStyleMap
}
}
}];
Eureka!
http://jsfiddle.net/G33PE/5/
var leanwxApp = angular.module('LeanwxApp', [], function () {});
var controllers = {};
var directives = {};
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0].parentElement, null)
}
}
}];
leanwxApp.controller(controllers);
leanwxApp.directive(directives);
So that just took lots of persistence and guessing. Perhaps the timeout is unnecessary but while debugging it seemed I only got the style value from the parent after the timeout occurred.
Also I'm not sure why but I had to go up to the parentElement to get the style (even though it would realistically be inherited (shrug)?)
Updated fiddle again
Did one without the timeout but just looking at the parentElement for the style and it seems to still work, so scratch the suspicions about the style not being available at all, it's just not available where I would expect it.
Also holy cow there are a lot of ways to debug in Chrome:
https://developers.google.com/chrome-developer-tools/docs/javascript-debugging
I used
debugger;
statements in the code to drop in breakpoints without having to search all the fiddle files.
One more quick update
The code below comes out of Boostrap-UI from the AngularUI team and claims to provide a means to watch the appropriate events (haven't tried this but it looks like it should help).
http://angular-ui.github.io/bootstrap/
/**
* $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
* #param {DOMElement} element The DOMElement that will be animated.
* #param {string|object|function} trigger The thing that will cause the transition to start:
* - As a string, it represents the css class to be added to the element.
* - As an object, it represents a hash of style attributes to be applied to the element.
* - As a function, it represents a function to be called that will cause the transition to occur.
* #return {Promise} A promise that is resolved when the transition finishes.
*/
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
var $transition = function(element, trigger, options) {
options = options || {};
var deferred = $q.defer();
var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
var transitionEndHandler = function(event) {
$rootScope.$apply(function() {
element.unbind(endEventName, transitionEndHandler);
deferred.resolve(element);
});
};
if (endEventName) {
element.bind(endEventName, transitionEndHandler);
}
// Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
$timeout(function() {
if ( angular.isString(trigger) ) {
element.addClass(trigger);
} else if ( angular.isFunction(trigger) ) {
trigger(element);
} else if ( angular.isObject(trigger) ) {
element.css(trigger);
}
//If browser does not support transitions, instantly resolve
if ( !endEventName ) {
deferred.resolve(element);
}
});
// Add our custom cancel function to the promise that is returned
// We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
// i.e. it will therefore never raise a transitionEnd event for that transition
deferred.promise.cancel = function() {
if ( endEventName ) {
element.unbind(endEventName, transitionEndHandler);
}
deferred.reject('Transition cancelled');
};
return deferred.promise;
};
// Work out the name of the transitionEnd event
var transElement = document.createElement('trans');
var transitionEndEventNames = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'transition': 'transitionend'
};
var animationEndEventNames = {
'WebkitTransition': 'webkitAnimationEnd',
'MozTransition': 'animationend',
'OTransition': 'oAnimationEnd',
'transition': 'animationend'
};
function findEndEventName(endEventNames) {
for (var name in endEventNames){
if (transElement.style[name] !== undefined) {
return endEventNames[name];
}
}
}
$transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
$transition.animationEndEventName = findEndEventName(animationEndEventNames);
return $transition;
}]);
The issue you'll face is that getComputedStyle is considered a very slow running method, so you will run into performance issues if using that, especially if you want angularjs to update the view whenever getComputedStyle changes.
Also, getComputedStyle will resolve every single style declaration possible, which i think will not be very useful. So i think a method to reduce the number of possible style is needed.
Definitely consider this an anti-pattern, but if you still insist in this foolishness:
module.directive('getStyleProperty', function($window){
return {
//Child scope so properties are not leaked to parent
scope : true,
link : function(scope, element, attr){
//A map of styles you are interested in
var styleProperties = ['text', 'border'];
scope.$watch(function(){
//A watch function to get the styles
//Since this runs every single time there is an angularjs loop, this would not be a very performant way to do this
var obj = {};
var computedStyle = $window.getComputedStyle(element[0]);
angular.forEach(styleProperties, function(value){
obj[value] = computedStyle.getPropertyValue(value);
});
return obj;
}, function(newValue){
scope.theStyle = newValue;
});
}
}
});
This solution works if you don't HAVE to have the directive on the child element. If you just place the declaration on the ng-repeat element itself, your solution works:
<div insert-style ng-repeat="x in ['blue', 'green']" class="{{x}}">
Fiddle

Resources