I tried to convert the normal jQuery own carousel into Angular Directive. It doesn't work for me, show some angular errors which I couldn't find what is the issue.
Controller
$scope.defaultsCarousel = {
'items': 4,
'itemWidth': 300,
'itemsDesktop': [1260, 3],
'itemsTablet': [930, 2],
'itemsMobile': [620, 1],
'navigation': true,
'navigationText': false
};
HTML (Jade)
custom-carousel(data-options="{{ defaultsCarousel }}", productid="#pl-1")
Directive
myApp.directive('customCarousel', function(){
function nextSlide(e) {
e.preventDefault();
e.data.owlObject.next();
};
function prevSlide(e) {
e.preventDefault();
e.data.owlObject.prev();
};
return{
restrict: 'E',
scope: {},
link: function($scope, el, attrs){
var options = $scope.$eval($(el).attr('data-options'));
var product_id = attrs.productid;
console.log(product_id);
$(product_id).owlCarousel(options);
var owl = $(product_id).data('owlCarousel');
$(product_id).parent().find('.slide-control.right').on('click', {owlObject: owl}, nextSlide);
$(product_id).parent().find('.slide-control.left').on('click', {owlObject: owl}, prevSlide);
}
}
ERROR
Syntax Error: Token '{' invalid key at column 2 of the expression [{{] starting at [{4}].
Your problem is at this line $scope.$eval($(el).attr('data-options'));. This produce a parse syntax error. You have two options to fix it:
OPTION 1: get the options from attrs parameter of link directive function. (PLUNKER)
app.directive('customCarousel', function() {
return {
restrict: 'E',
link: function(scope, el, attrs) {
var options = angular.fromJson(attrs.options);
var product_id = attrs.productid;
//..Rest of your logic
}
}
});
OPTION 2: get the options using scope one way binding. (PLUNKER)
app.directive('customCarousel', function() {
return {
restrict: 'E',
scope: {
options: '#',
productid: '#'
},
link: function(scope, el, attrs) {
var options = angular.fromJson(scope.options);
var product_id = scope.productid;
//..Rest of your logic
}
}
});
As you can see I'm getting the html data-options attribute as just options. That's because angularjs directives will ignore data-* prefix in all HTML elements and attributes names.
More info:
Check this post about difference between ng-app and data-ng-app
Check basic data-* prefix docs in W3Schools
Related
Yesterday I find solution for my angular carousel,
but its has some issue with binding.
Every time data of carousel should changing and now this example doesn't work , but when i add :: before openGame.availableMarketGroups in HTML ng-repeat , its works , but one time binding only .
How can i fix it ?
Any ideas ?
Thanks
The source
VBET5.directive("owlCarousel", function() {
return {
restrict: 'E',
transclude: false,
link: function (scope) {
scope.initCarousel = function(element) {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr('data-options'));
// combine the two options objects
for(var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
// init carousel
var curOwl = $(element).data('owlCarousel');
if(!angular.isDefined(curOwl)) {
$(element).owlCarousel(defaultOptions);
}
scope.cnt++;
};
}
};
})
.directive('owlCarouselItem', [function() {
return {
restrict: 'A',
transclude: false,
link: function(scope, element) {
// wait for the last item in the ng-repeat then call init
if(scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
Here is the HTML
<data-owl-carousel data-options="{navigation: true, pagination: false, rewindNav : true}">
<div class="market-carousel">
<div owl-carousel-item="" class="carousel-item"
ng-repeat="group in openGame.availableMarketGroups track by group.id">
<span>...</span>
</div>
</div>
</data-owl-carousel>
I need to render dynamic template from database and also need to bind the variables to expressions.
My response JSON will look like this,
[{
"htmlTemplate": "<div>{{name}}</div><div>{{age}}</div>",
"bindData": {
"name": "safeer",
"age" : "25"
}
}, {
"htmlTemplate": "<span>{{name}}</span><div>{{address}}</div>",
"bindData": {
"name": "john",
"address":"qwerty"
}
}, {
"htmlTemplate": "<h4>{{name}}</h4><h2>{{country}}</h2>",
"bindData": {
"name": "james",
"country":"India",
"state" : "Kerala"
}
}]
I have created a directive as per the answer to the question Compiling dynamic HTML strings from database
In html, demo.html
<div dynamic="html"></div>
In directive, directive.js
var app = angular.module('app', []);
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
It will render html template and replace variable with $scope variable name finely.
But I need to render each htmlTemplate with its corresponding bindData. That is render each template with isolated scope data. Need to generalize directive.
Any help appreciated! Thanks in advance.
When you receive the JSON data you can use angular.fromJSON to decode the json string into the array (unless you are using the $http.get() which already does that for you)...
//request the JSON from server
jsonString = SomeFactory.fetchDataFromServer();
$scope.dataArray = angular.fromJson(jsonString);
...and then use the ngRepeat to create multiple elements:
<div ng-repeat="element in dataArray" dynamic="element"></div>
Modify your directive like this:
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.bindData = {};
scope.$watch(attrs.dynamic, function(dynamic) {
console.log('Watch called');
ele.html(dynamic.htmlTemplate); //here's your htmlTemplate being compiled
$compile(ele.contents())(scope);
scope.bindData = dynamic.bindData; //here's your bound data ifyou need the reference
scope.name = dynamic.bindData.name; //bound data property name
}, true); //deep watch
}
};
});
Edit: Or you could simply pass element.htmlTemplate and element.bindData separately to the directive through two separate attributes as mentioned in an answer by user Vineet, which makes more sense.
Edit2: Fixed some bugs.
Here's the fiddle: Fiddle
With generalising I assume you want to make it like a component and resuse it whenever you want.
I would suggest to separate the scope of this directive. Bind your "html" and "name" both of them from parent.
AFTER EDITING:
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
scope : {
html : "=dynamic"
},
link: function (scope, ele, attrs) {
ele.html(scope.html);
$compile(ele.contents())(scope);
var unbindWatcher = $scope.$watch(
attrs.bindData,
function(binddata) {
if ( binddata ) {
angular.extend(scope,binddata);
// Once the data has been binded to scope,
// there's no more need to watch the change
// in the model value.
unbindWatcher();
}
}
);
}
};
});
And your html as:
<div dynamic="html" bindData="bindData"></div>
In this edit, what I have done is following 3 things:
1) Watching your attribute bindData - which will contain your db stored bind data. I have not included this in scope of directive because I want to include its properties in the scope so that you can bind your 'name', etc. from bindData in db to the templates.
2)Extending the bindData object into scope of your directive. Now your scope will have 'name', etc.
3)Destroying the watcher as soon as bindData is read for first time. This ensures that any change in bindData variable inside parent will not be conveyed to directive after first bind.
But still keep the scope separated to make it work properly.
Hope this will solve your problem
I am trying to make custom directive to display:
Will {{selectedAsset}} go {{red}} or {{green}}?
It looks like this on the screen:
AUD/USD will down(red color) or up(green color)?
The {{red}} and {{green}} part should have its own color . Hence I am trying to wrap it with a span having desired classes.
But it is not working, below is the code:
<trade-header selected-asset="selectedAsset" red="widgetMessage.goDown" green="widgetMessage.goUp"></trade-header>
var widgetMessage = {
"Trade_Header": "Will {{selectedAsset}} go {{red}} or {{green}}?",
"goUp": "up",
"goDown": "down"}
myApp.directive("tradeHeader", function($sce) {
return {
restrict: "AE",
scope: {
selectedAsset: "=",
green:"=",
red:"="
},
link: function(scope, element, attrs) {
scope.green = $sce.trustAsHtml('<span class="goOrDonwLabel upGreen">' + scope.green + '</span>');
},
template: widgetMessage.Trade_Header,
}
});
widgetMessage.Trade_Header need to be variable cause the design.
This consequence will be : "AUD/USD will down or <span class="Green">up</span>?"
I need it to be compiled as HTML, any suggestions?
Your html should be in the template. When you're trying to manipulate HTML in code, that's usually a sign you're not using templates the right way. Try something like this:
<trade-header selected-asset="selectedAsset" red="widgetMessage.goDown" green="widgetMessage.goUp"></trade-header>
var widgetMessage = {
"Trade_Header": 'Will {{selectedAsset}} go {{red}} or <span ng-class="greenLabel">{{green}}</span>?',
"goUp": "up",
"goDown": "down"}
myApp.directive("tradeHeader", function($sce) {
return {
restrict: "AE",
scope: {
selectedAsset: "=",
green:"=",
red:"="
},
link: function(scope, element, attrs) {
scope.greenLabel = widgetMessage.goUp; // logic for setting green's class here
},
template: widgetMessage.Trade_Header,
}
});
I am trying to build an application that uses angularjs and jquery mobile:
I have stumbled upon a problem when trying to filter model for jquery mobile slider.
The problem is that I need to modify data in directive (that is displayed for user) and that is used for further processing.
I found a posible way of achieving my goal on this post: Using angularjs filter in input element
However this doesn't work, probobly beause I am using compile function instead of link (because I need to trigger jquery slider create event before binding any data. If I don't do so, the neccessery markup for jquery mobile slider is not generated).
So anyway what happens is that after passing model trought another "filter" directive, the value is set to "1" even though it should be "50". And on top of that slider starts to act strangely, it only allows part of values to be set, for example "10" thought it should be "100".
Here's how I define my directives:
mcb.directive('jparser', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (s, e, a, ngModel) {
ngModel.$parsers.push(function (input) {
console.log('parser: ' + input);
return Math.round(input * 10);
});
ngModel.$formatters.push(function (input) {
console.log('formater: ' + input);
return Math.round(input / 10);
});
}
};
});
mcb.directive('jSlider', function () {
return {
scope: {
id: '#',
label: '#',
value: '=',
min: '=',
max: '=',
step: '#',
start: '&',
stop: '&'
},
restrict: 'A',
replace: false,
templateUrl: 'jslider.html',
compile: function (e) {
e.trigger('create');
return {
post: function (s, e, a) {
e.on('slidestart onfocus', function () {
return s.start();
});
e.on('slidestop', function () {
return s.stop();
});
e.on('focus', '.ng-valid-number', function () {
return s.start();
});
e.on('blur', '.ng-valid-number', function () {
return s.stop();
});
s.$watch('value', function (nv) {
console.log('nv:'+nv);
return e.find('#' + a.id).slider('refresh');
});
s.$watch('min', function () {
return e.find('#' + a.id).slider('refresh');
});
s.$watch('max', function () {
return e.find('#' + a.id).slider('refresh');
});
}
};
}
};
});
Sorry if it's messy question, I was trying to describe the problem as clear as possible.
Here's a plunker, with the problem in action: http://plnkr.co/edit/k8u0UgbIX8qLCGX1Yw3A?p=preview
I am trying to write a directive for the jeditable plugin so when it changes the value, it will change also edit the model of the edited element.
So i wrote something like that, JS Fiddle
but i don`t know how to get the object that bound to the object in the list.
JS:
var app = angular.module("app", []);
app.controller('ctrl', function ($scope) {
$scope.lst = [{
id: 1,
name: "item1"
}, {
id: 1,
name: "item1"
}, {
id: 2,
name: "item2"
}, {
id: 3,
name: "item3"
}, {
id: 3,
name: "item3"
}];
});
app.directive('uiEditable', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.editable("/echo/json/", {
onblur: 'submit',
onsubmit: function (response, settings) {
//here i need to update the model
}
});
}
};
});
This uses ngModel to update back to the model. (so don't forget ng-model on element)
app.directive('uiEditable', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
element.editable(function (val) {
var tVal = $.trim(val);
if (ngModel.$viewValue !== tVal)
scope.$apply(function () { return ngModel.$setViewValue(tVal); });
return tVal;
});
}
};
});
Why are you using the jeditable plugin? This plugin seems to only duplicate in jQuery what you could already do in angular using ng-model alone and no plugin required.
If you just want to create text which can be edited in place like jEditable does, instead of creating a custom directive simply using ng-submit, ng-click, ng-hide and ng-model. Here's a rough example.
The view:
<form ng-submit="submit()">
<div ng-hide="showEdit"
ng-click="showEdit = true">
{{foo.bar}}
</div>
<div>
<input type="text"
ng-show="showEdit"
ng-model="foo.bar" />
</div>
<a href="#" ng-show="showEdit"
ng-click="submit();">done</a>
</form>
And the controller:
app.controller('myCtrl', function($scope) {
$scope.foo = {
bar: 'some text'
};
$scope.showEdit = false;
$scope.submit = function() {
// hide the edit field
$scope.showEdit = false;
// submit form
console.log('submit form');
}
});
Pass your item in in an isolated scope:
app.directive('uiEditable', function(){
return {
restrict: 'A',
scope: {
item: '='
},
link: function(scope, element, attrs){
element.editable("/echo/json/", {
onblur: 'submit',
onsubmit: function(response, settings){
alert(scope.item);
}
});
}
};
});
'scope.item' will now give you a reference to the item inside your directive.