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
Related
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
I'm working on an angular wrapper for the jquery-knob widget. The following works as long as the maximum value doesn't change. If it does, the ng-model binding is lost. If I don't destroy the knob widget at the beginning of the watch, the max value doesn't change.
//Directive
app.directive('knobWidget', function () {
return {
scope: {
maxbinding: "=maxbinding",
maxbindingprop: "#maxbindingprop"
},
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ngModel) {
ngModel.$render = function () {
$(elem).val(ngModel.$viewValue).trigger("change");
};
scope.$watch('maxbinding', function (newVal) {
$(elem).knob('destroy');
$(elem).knob({
min: 0,
max: scope.maxbinding[scope.maxbindingprop],
value: ngModel.$viewValue,
change: function (changeVal) {
scope.$apply(function () {
ngModel.$setViewValue(changeVal);
});
}
});
});
}
};
});
//Markup
<input knob-widget data-min="0" maxbinding="arr" maxbindingprop="length" ng-model="currentStop" />
Doing:
$(elem).knob('max', scope.maxbinding[scope.maxbindingprop]);
doesn't work either. Any ideas?
Using trigger('configure') followed by trigger('change') should do the trick
$(elem).trigger('configure', {
'max': scope.maxbinding[scope.maxbindingprop];
});
$(elem).trigger('change');
Source
Here is my plnkr with my progress so far: http://plnkr.co/edit/iEHMUMlASZaqdMQUeF7J?p=preview
I'm having problems implementing the following functionality however.
When an item on the list is clicked, I need to disable the remaining items on the list. ie, another request should not take place, and these remaining items' colour should change to indicate the disabled state.
Once the request has taken place, then the entire list should go back to the original state.
Edit: I've made some progress. Although a bit messy it's getting me a bit closer. My problem is the following line:
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
This prevents the click event running more than once at a time. However it's stopping the click event from running all together once its run for the first time. I would like to be able to re-run the process once it is complete an unlimited amount of times.
Directive:
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
elem.parent().removeClass('item-selected');
});
;
}, 2000);
});
}
};
});
The entire app code including directive:
var app = angular.module('listtestApp', []);
app.service('ListService', function ($http) {
var data = [
'alpha',
'bravo',
'charlie',
'delta',
'foxtrot'
];
return {
getData : function () {
return data;
},
selectItem : function () {
return $http({ method: 'GET', url : '/data/list.json'});
}
}
});
app.controller('ListController', function ($scope, ListService) {
$scope.list = ListService.getData();
$scope.foo = 'Bar';
});
app.controller('ItemController', function ($scope, ListService) {
});
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
});
;
}, 2000);
});
}
};
});
html markup below:
<body ng-app="listtestApp">
<div ng-controller="ListController">
<div ng-repeat="item in list" list-item>
</div>
</div>
</body>
You have several solutions at your disposal :
Check that any element has the pending or success or error class
use your function scope to store it in a variable
EDIT : if you want to re-enable selection after the request has been posted, you could use something like this (variant of version #1)
I am using in my application angular.js and jquery autocomplete.
So, I would like to create an angular directive, which wrap jquery autocomplete:
'use strict'
angular.module('nsi')
.directive('autoComplete', function () {
return {
restrict: 'A',
scope: {
httpService: "=",
renderItem: '=',
ngModel: '=',
minLength: '=',
onSelect: '='
},
link: function (scope, elem) {
elem.autocomplete({
source: function (request, response) {
scope.httpService(request.term).then(function (data) {
response(
$.map(data.items, function (item) {
return scope.renderItem(item);
}
)
);
});
},
minLength: scope.minLength,
select: function (event, ui) {
if (scope.onSelect) {
scope.onSelect(ui.item.item);
}
scope.$apply(function () {
scope.ngModel = ui.item.item;
});
}
})
;
}
};
});
in my controller I initialize necessary parameters for it:
$scope.supplierRender = function(item){
return {
label: item.supplierName,
value: item.supplierName,
item: item
}
};
$scope.httpSupplierService = function(suggest){
return SupplierService.getSuppliers('%' + suggest + '%');
};
$scope.supplierSelect = function(val) {
$scope.employee.supplier.id = val.id;
};
In my html view I start my directive:
<input type="text"
auto-complete
min-length="3"
http-service="httpSupplierService"
render-item="supplierRender"
on-select="supplierSelect"
ng-model="employee.supplier"
class="form-control"
value="employee.supplier.supplierName" />
The problem is, that when I am opening my employee in edit window, in autocomplete input i see [object Object]. So the queston is: "How to bind ngModel to employee.Supplier", and value of input to specific field of object (employee.supplier.supplierName)
Try to set ng-model:
ng-model="employee.supplier.supplierName"
Generally <input> takes value from directive ng-model so you don't need provide value=..
This is a test: Fiddle
The value=.. attribute works when ng-model not defined for input
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.