I have a controller that fetches images using service and builds up DOM as following,
<ul>
<li ng-repeat="image in images">
<img parentimageclick="{{ image.standard_resolution.url }}" ng-src={{image.thumbnail.url}} />
</li>
</ul>
Now using directive I'm calling a service via same controller
Products.directive("parentimageclick",function(){
var parentimageclick = function(scope,element,attrs) {
element.bind('click',function(){
scope.callChild(attrs.src);
});
};
return parentimageclick;
});
As you see callChild for controller is called which calls service
$scope.callChild = function(data) {
packImage.prepareSend(data);
}
Service code is,
Products.factory('packImage',function($rootScope){
var packImage = {};
packImage.img = "";
packImage.prepareSend = function(img) {
this.img = img;
this.Send();
}
packImage.Send = function(){
$rootScope.$broadcast('takeimage');
}
return packImage;
});
So basically i use this service to access the clicked image in another controller as follows,
Products.controller('PackCtrl',function($scope,packImage){
var images = [];
$scope.$on('takeimage', function() {
//console.log(packImage.img);
var data = {
url: packImage.img,
};
images.push(data);
$scope.images = images;
$scope.$apply();
});
});
Now problem with the above code is it does populate the dom as model updates but what I'm really trying to achieve is different. cuz my DOM remain same.
For eg, i have a hardcode list in my dom as follows,
<ul>
<li></li>
<li></li>
//Ten of them
</ul>
So i couldn't change the above structure and i need some functionality where if the image is fetched in the controller it needs to find the empty list item and append it there. This has to be done until list is finished.
Update:
This http://jsfiddle.net/CQzu5/ should be about rough demo of what i want to do? hope there is an angular way to do same.
Related
I'm using the GridLoadingEffects library by Codrops along with Angular JS to feed it list items with data from wordpress. Everything works in terms of Angular retrieving the data via $http request and storing the variables asynchronously. The data is processed and binded to HTML via ng-bind-html.
<body ng-app="myApp">
<div class="container" ng-controller="myController">
<ul class="grid effect-6" id="grid" ng-bind-html="bindHTML(html_code)"></ul>
</div>
</body>
After a slight delay as a result of retrieving the data, I can see that the HTML is in fact loaded into the DOM in the inspector. However, the problem is that the GridLoadingEffects effect immediately sees that there are no list items in the DOM, and therefore throws an undefined error.
I'm not an experienced web developer, so I can't precisely determine where the call is made to check for the DOM's li elements and apply the effect, and this library has many dependencies including modernizr.js, masonry.js, imagesloaded.js, AnimOnScroll.js, and more. What confuses me further is that these files are loaded in different places in the HTML, some in the head and some at the end of the body. So in terms of the execution flow, I'm really lost as to how to solve this problem. I'm not sure but I think this function is what instantiates the effect.
<script>
window.onload = function() {
new AnimOnScroll( document.getElementById( 'grid' ), {
minDuration : 0.4,
maxDuration : 0.7,
viewportFactor : 0.2
} );
}
</script>
It is to be placed at the end of the body, as per the example html file provided by Codrops. I think that in order to rectify the issue, the function above has to run once Angular has fed the data to the ul tag. Even though there's a window.onload attached to it, it probably doesn't consider that there's more to load with angular.
Am I correct in my assumptions? I'm sure there's an easy fix to this. Can anyone help?
UPDATE:
Here is how the $http request is initiated:
(function() {
'use strict'
angular.module('myApp', [])
.controller('myController', myController);
myController.$inject = ['$scope', '$sce','$http']
function myController ($scope, $sce, $http) {
$http({
method : "GET",
url : myURL
})
.then(function(response) {
$scope.myData = response.data;
var list = new Array
for (var i = 0; i < $scope.myData.length; i++) {
var string = '<li><figure class="blurfilter semitransparent"><img src=myURL><figcaption><h3>' + $scope.myData[i]['title']['rendered'] + '</h3></figcaption></figure></li>';
list.push(string)
}
$scope.html_code = list.join("")
$scope.bindHTML = function(html_code) {
return $sce.trustAsHtml(html_code)
};
});
};
})();
UPDATE 2:
My HTML now using ng-repeat to separate model and view:
<li ng-repeat="(key, val) in myData">
<h3>{{val.title.rendered}}</h3>
</li>
And my angular code (omitted the http requests):
(function() {
'use strict'
angular.module('myApp', [])
.controller('myController', myController);
myController.$inject = ['$scope', '$sce','$http']
function myController ($scope, $sce, $http) {
$scope.$watch('myData', function() {
// if the myData is loaded
if ($scope.myData.length > 0) {
new AnimOnScroll( document.getElementById( 'grid' ), {
minDuration : 0.4,
maxDuration : 0.7,
viewportFactor : 0.2
});
}
});
Now I get $scope.myData is undefined. $scope.myData is defined in the .then of my http request. Maybe it's helpful to mention that this error points to a line in the angular.min.js file, not to my app.js.
You can try to use $scope.$watch function in your angular controller on the data variable after you get the data using $http service.
$scope.$watch('myData', function() {
// if the myData is loaded
if ($scope.myData && $scope.myData.length > 0) {
// your code
}
});
I understand my problem deviated a little from running a function after data has been retrieved to running the function after the retrieved data has populated the DOM. For anyone stuck on the same problem (not when the data is retrieved but after retrieved and DOM has been populated), you need to use directives. Check this post: ng-repeat finish event. Your solution is here.
I have a custom directive which is very similar to a drop down. Items in the drop down menu are associated with file names of certain videos. A div below the drop down displays a default video file (I have done this via Videoangular).
Whenever I make a selection from the drop down menu, I am changing the default variable containing filename (String) to the one I want. But, the same is not reflected in the div.
My objective is to refresh div containing the video with appropriate video whenever a selection is made from the drop down menu.
This is my controller:
angular.module('myApp',
[
"ngSanitize",
"com.2fdevs.videogular",
"com.2fdevs.videogular.plugins.controls",
"com.2fdevs.videogular.plugins.overlayplay"
]
)
.controller('ctrl',
["$rootScope", "$scope", "$state", "$log", "Restangular",
function ($rootScope, $scope, $state, $log, Restangular) {
'use strict';
var vm = this;
//DEFAULT VIDEO FILE NAME
vm.fields = "WaterCool1";
this.config = {
sources: [
{src: "assets/data/"+vm.fields+".mp4", type: "video/mp4"}
]
};
vm.loadPage = loadPage;
vm.coolingSystemTypeSelector = {coolingSystemTypeSelector:{}};
getAll('cooling-system-type').then(
function(objs) {
$log.debug("get Cooling System Type", objs);
vm.coolingSystemTypeSelector = objs.selector;
vm.fields = "WaterCool1";
vm.coolingSystemTypeSelector.onSelect = function (selection) {
if(!selection){
return;
}
$log.debug("Cooling System Type Selection == ", selection);
if(selection.label==="ACC"){
vm.fields = "AirCool";
}else if(selection.label === "WCC-CT"){
vm.fields = "WaterCool1";
}else if(selection.label === "WCC-DC"){
vm.fields = "WaterCool2";
}
};
}
);
///.....
}
]
);
This is my HTML:
<div>
<selector form="form" columns=vm.columns target="vm.coolingSystemTypeSelector"></selector>
</div>
<hr>
<div id="refreshThisDiv">
<!--I want to refresh this div-->
<videogular vg-theme="vm.config.theme">
<!--VIDEOGULAR CODE-->
</videogular>
</div>
What you need is not to refresh the div. You need angular to refresh the div based on you modifying bound properties.
Your declaration of this.config is actually static and you are never modifying the value of this.config.sources src after instantiation. As that code is running only once it will forever remain as "assets/data/WaterCool1.mp4".
What you need to do instead at least, is to modify this value upon selection of an option in the drop-down. Something like:
// ...
var that = this;
getAll('cooling-system-type').then(
// ... inside onSelect ...
if(selection.label==="ACC") {
that.config.sources = [{src: "assets/data/AirCool.mp4", type: "video/mp4"}];
}
// ...
Even then, with this code, you might need to trigger a manual $apply as angular may not be aware of your change to the field via this onSelect event handling. Ideally you will be able to bind the event to the function directly in HTML by using ng-change and avoid the need for that.
If you provide a full sample (https://plnkr.co/edit/), it's easier to guide you to a solution and explain in without the need to rewrite your original code.
I have implemented a slider with the angular-carousel package using ng-repeat. It works correctly, but it adds an extra blank slide at the end of the carousel. I searched online and other people had a similar error, because of an extra element at the end of their carousel, but I don't have anything like that in my code. And I don't see extra elements when I inspect the dom in the browser either.
I'm using a directive to set the height of the carousel to the tallest image, but it has the same issue whether or not I use a directive. Everything works the way I want it except for the extra slide at the end of the carousel.
I'll paste my code below, please let me know if you need more info or clarification.
Thanks in advance for all the help!
The template looks like this:
<!-- Begin Image section/slider -->
<div data-ng-show="isCurrentPage('/')" data-ng-controller="HeaderController" data-ng-init="getSiteStyle();" class="clearfix">
<ul rn-carousel rn-carousel-controls-allow-loop rn-carousel-controls rn-carousel-auto-slide="3" data-slider-directive id="upcoming-events-carousel" class="image" style="margin-left:0;margin-bottom:0;">
<li data-ng-repeat="futureEvent in futureEvents" data-ng-if="futureEvent.eventHomepageImage">
<div outer-height class="layer" data-slider-slide-directive>
<a href="/{{futureEvent.eventUrl}}">
<img class="carousel-img" data-ng-src="/app/uploads/{{ futureEvent.eventHomepageImage }}" />
</a>
</div>
</li>
</ul>
</div>
the sliderSlideDirective looks like this:
'use strict';
const jQuery = require('jquery');
const sliderSlideDirective = (app) => {
app.directive('sliderSlideDirective', ['$timeout',
function($timeout) {
const sliderSlideDirectiveDefinitionObject = {
restrict: 'A',
scope: true,
link: function postLink(scope, element, attrs) {
$timeout(function() {
//get height of current slide in list and push the height into the height array
let elemHeight = jQuery(element[0]).height();
//push that height onto array of heights from parent scope
scope.$parent.sliderImgsHeights.push(elemHeight);
//assign the tallest height in array to newHeight variable using es6 spread operator
let newHeight = Math.max(...scope.$parent.sliderImgsHeights);
jQuery('#upcoming-events-carousel').height(newHeight);
});
}
};
return sliderSlideDirectiveDefinitionObject
}
])
};
module.exports = sliderSlideDirective;;
The controller looks like this:
'use strict';
const HeaderController = (app) => {
app.controller('HeaderController', ['$scope', '$http', 'headerRESTResource',
function($scope, $http, resource) {
$scope.errors = [];
$scope.siteStyle = [];
$scope.sliderImgsHeights = [];
let SiteStyle = resource();
$scope.getSiteStyle = () => {
SiteStyle.getSiteStyle(function(err, data) {
if (err) {
return $scope.errors.push({
msg: 'could not retrieve team members'
});
};
$scope.siteStyles = data;
})
};
}
])
}
module.exports = HeaderController;
So after some experimenting, I found that the problem was that some of the items in the array I was returning to make the slides had a null value for their image, so no slide was created, which I thought would be taken care of by data-ng-if="futureEvent.eventHomepageImage", but the items with null for the images still add to the length of the array and angular-carousel uses the length of the array to make the offsets for the slides. So, even though it wasn't adding the blank slides, angular-carousel added extra padding to account for an image that wasn't there, because of the length of the array.
To fix it I looped over the array and pushed the images that were there into a new array and returned the new array containing only images to make the carousel.
Hope this helps anyone else who runs into the same issue :-)
I have been looking at this for quite few hours and I don't think I am able to see the solution.
This is my router.js:
define('router', ['jquery', 'config', 'nav','store'], function ($, config, nav, store) {
var
concepTouch = Sammy('body', function () {
// This says to sammy to use the title plugin
this.use(Sammy.Title);
this.use(Sammy.Mustache);
// Sets the global title prefix
this.setTitle(config.title.prefix);
// So I can access sammy inside private methods
var sammy = this;
function establishRoutes() {
// Defines the main container for content then
var mainConainer = $(config.mainContentContainerId);
// Adds animation loading class to the main container
mainConainer.addClass(config.loadingAnimationCssClass);
// iterates through routes defined in config class then
_.forEach(config.appRoutes, function(obj, key) {
// defines each one as a route
sammy.get(obj.hashV, function(context) {
// Store the requested route as the last viewed route
store.save(config.stateKeys.lastView, context.path);
// Fetches its html template
context.render(obj.tmpltURL, { 'routeData': context.params })
// Appends that htmlo template to the main container and removes loading animation
.then(function(content) {
mainConainer.removeClass(config.loadingAnimationCssClass).html(content);
});
// Finally adds the route title to the prefix
this.title(obj.title);
});
// Overriding sammy's 404
sammy.notFound = function () {
// toast an error about the missing command
toastr.error(sammy.getLocation() + ' Does not exist yet!');
// Go to last visited anf if not
sammy.setLocation(
store.fetch(config.stateKeys.lastView) || config.getDefaultRoute()
);
};
});
}
// Calls for routes to be established
establishRoutes();
}),
// runs concep touch as a sammy App with the initial view of default route
init = function () {
// Try to get today's last visit and if not available then fallback on default
concepTouch.run(store.fetch(config.stateKeys.lastView) || config.getDefaultRoute());
// Make the correct nav item active and add Click handlers for navigation menu
nav.setStartupActiveClass(store.fetch(config.stateKeys.lastView) || sammy.getLocation())
.addActiveClassEventHandlers();
};
return {
init: init,
concepTouch: concepTouch
};
});
This when I submit the search form gets this template for me:
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
searchresults.get(to Some how Get routeData.term);
});
</script>
and I can not find the right way to make Mustache pass the data from this line of router.js context.render(obj.tmpltURL, { 'routeData': context.params }) to the {{routeData.term}} inside the template.
{{routeData}} on its own returns `SAMMY.OBJECT: {"TERM": MY SEARCH TERM}`
which I can't navigate to the property i want to from it using . notation. Furthermore even if that worked it can not be passed into Javascript which is what I really need as
searchresults.init(); is waiting for this paramter `searchresults.init(routeData.term);`
Or maybe the answer is to find a way to access sammy's context here? outside of sammy in order to get the params? something like Sammy.Application.context.params['term'] but ofcourse application has no such method so don't know!? :(
Am I going totally the wrong way about it? How Can I easily pass the query string params as accessible objects inside my template so knockout can use it.
Your help is greatly appreciated.
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{#routeData}}{{term}}{{/routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
var searchTerm = "{{#routeData}}{{term}}{{/routeData}}";
searchresults.get(searchTerm);
});
</script>
Is it at all easy to use jQuery.sortable on ng-repeat elements in AngularJS?
It would be awesome if re-ordering the items automatically propagated that ordering back into the source array. I'm afraid the two systems would fight though. Is there a better way to do this?
Angular UI has a sortable directive,Click Here for Demo
Code located at ui-sortable, usage:
<ul ui-sortable ng-model="items" ui-sortable-update="sorted">
<li ng-repeat="item in items track by $index" id="{{$index}}">{{ item }}</li>
</ul>
$scope.sorted = (event, ui) => { console.log(ui.item[0].getAttribute('id')) }
I tried to do the same and came up with the following solution:
angular.directive("my:sortable", function(expression, compiledElement){
return function(linkElement){
var scope = this;
linkElement.sortable(
{
placeholder: "ui-state-highlight",
opacity: 0.8,
update: function(event, ui) {
var model = scope.$tryEval(expression);
var newModel = [];
var items = [];
linkElement.children().each(function() {
var item = $(this);
// get old item index
var oldIndex = item.attr("ng:repeat-index");
if(oldIndex) {
// new model in new order
newModel.push(model[oldIndex]);
// items in original order
items[oldIndex] = item;
// and remove
item.detach();
}
});
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// clear old list
model.length = 0;
// add elements in new order
model.push.apply(model, newModel);
// presto
scope.$eval();
// Notify event handler
var onSortExpression = linkElement.attr("my:onsort");
if(onSortExpression) {
scope.$tryEval(onSortExpression, linkElement);
}
}
});
};
});
Used like this:
<ol id="todoList" my:sortable="todos" my:onsort="onSort()">
It seems to work fairly well. The trick is to undo the DOM manipulation made by sortable before updating the model, otherwise angular gets desynchronized from the DOM.
Notification of the changes works via the my:onsort expression which can call the controller methods.
I created a JsFiddle based on the angular todo tutorial to shows how it works: http://jsfiddle.net/M8YnR/180/
This is how I am doing it with angular v0.10.6. Here is the jsfiddle
angular.directive("my:sortable", function(expression, compiledElement){
// add my:sortable-index to children so we know the index in the model
compiledElement.children().attr("my:sortable-index","{{$index}}");
return function(linkElement){
var scope = this;
linkElement.sortable({
placeholder: "placeholder",
opacity: 0.8,
axis: "y",
update: function(event, ui) {
// get model
var model = scope.$apply(expression);
// remember its length
var modelLength = model.length;
// rember html nodes
var items = [];
// loop through items in new order
linkElement.children().each(function(index) {
var item = $(this);
// get old item index
var oldIndex = parseInt(item.attr("my:sortable-index"), 10);
// add item to the end of model
model.push(model[oldIndex]);
if(item.attr("my:sortable-index")) {
// items in original order to restore dom
items[oldIndex] = item;
// and remove item from dom
item.detach();
}
});
model.splice(0, modelLength);
// restore original dom order, so angular does not get confused
linkElement.append.apply(linkElement,items);
// notify angular of the change
scope.$digest();
}
});
};
});
Here's my implementation of sortable Angular.js directive without jquery.ui :
https://github.com/schartier/angular-sortable
you can go for ng-sortable directive which is lightweight and it does not uses jquery. here is link ng-sortable drag and drop elements
Demo for ng-sortable