The Problem
Unable to update Model;
Using the set function I can change the ngModel value, and through the pipeline function $parsers, however the Del function uses splice to process the array, but it can not pass the pipeline function and realizes the update model value.
I tried to use $scope.$apply () to execute after ngModel.$setViewValue ($scope.images) and still can't be solved.
The version of the angular used is 1.6.6.
Online code, online code links, I hope you can help me to see where the problem is in the end.
View code
<div ng-controller="appController">
<div image-uploads ng-model="files"></div>
<p style="display: block; color: red">{{files}}</p>
</div>
Javascript code
var app = angular.module('app', []);
app.controller('appController', function ($scope) {
$scope.files = '1,2,3,4';
});
app.directive('imageUploads', function () {
return {
require: '?^ngModel',
restrict: 'EA',
template: '<div class="image-upload-box"><p class="image_upload" ng-repeat="image in images track by $index"><button ng-click="set()">setModel</button><button ng-click="del($index)">{{image}}</button></p></div>',
link: function ($scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (modelValue) {
var images = new Array();
if (!ngModel.$isEmpty(modelValue)) {
var values = modelValue.split(",");
for (var j = 0; j < values.length; j++) {
images.push({
'id': values[j]
});
}
}
return images;
});
ngModel.$parsers.push(function (viewValue) {
var s = "";
for (var j = 0; j < viewValue.length; j++) {
if (viewValue[j].id != null) {
if (j > 0) {
s += ",";
}
s += viewValue[j].id;
}
}
return s;
});
ngModel.$render = function () {
$scope.images = ngModel.$viewValue;
};
$scope.del = function (i) {
$scope.images.splice(i, 1);
ngModel.$setViewValue($scope.images);
};
$scope.set = function () {
console.log('set');
$scope.images = [{id: 5}, {id: 6}, {id: 7}];
ngModel.$setViewValue($scope.images);
}
}
};
});
Related
I am using Angularjs 1.6, I made this transclusion type of thing.
var app = angular.module('plunker', []);
app.controller("mainCtrl", function ($scope) {
$scope.testmodel1 = {
testmodel1prop: "testmodel1prop"
};
$scope.testmodel2 = {
testmodel2prop: "testmodel2prop"
}
})
app.directive('tabs', function ($compile) {
return {
restrict: 'E',
scope: {},
transclude: true ,
link: function (scope, el, attrs, ctrl, transcludeFn) {
// transcluded content's scope should inherit from parent
transcludeFn(scope.$parent, function (clonedTranscludedContent) {
var tabs = [];
for (var i = 1; i < clonedTranscludedContent.length; i = i + 2) {
tabs.push(clonedTranscludedContent[i]);
}
for (var i = 0; i < tabs.length; i++) {
debugger;
var jqueryTab = $(clonedTranscludedContent[1]);
var stringTab = jqueryTab.prop('outerHTML');
var model = jqueryTab.attr('model');
var pocoModelFromParent = scope.$parent[model];
var newScope = angular.merge(scope.$parent.$new(), pocoModelFromParent)
var linkFn = $compile(stringTab);
var compiledContent = linkFn(newScope);
el.append(compiledContent);
}
});
}
};
});
app.directive('test', function () {
return {
restrict: 'E',
scope: {},
link: function ($scope, $element, attr) {
$scope.var1 = "test";
},
template: '<p>{{ var1 }}</p>'
};
});
<div ng-app="plunker" >
<div ng-controller="mainCtrl">
<tabs>
<tab model="testmodel1" title="tab2">{{ testmodel1prop }}</tab>
<tab model="testmodel2" title="tab1"> {{ testmodel2prop }} </tab>
</tabs>
</div>
</div>
I would like to get some feedback if this is okay or not.
It works perfectly by the way.
The first thing that pops into my mind is why does 'clonedTranscludedContent' has a sort of empty object at position 1 3 5 7 and so on.I have yet to figure out what that is, probably the ::before in chrome?
I am planning to use this on production actually.
I have a problem with remove and edit cookie object from $cookies in angularjs. I want to make a shop and I am adding products to $cookies global variable with putObject function (I didn't use put because I have more than one argument). I want to add function to remove and edit product from shop and remove and edit only one object from cookie. Please help me!
Here is a fragment of my code (I want to remove/edit object from 'products' cookie):
app.controller('Store', ['$scope', '$cookies', 'x2js', '$http',
function($scope, $cookies, x2js, $http){
this.products = $cookies.getObject('products');
if(!this.products) {
var self = this;
$http.get('assets/xml/products.xml').success(function(data) {
self.products = data.products.product;
for(var i = 0; i < self.products.length; i++) {
self.products[i].id = parseInt(self.products[i].id);
self.products[i].netto = self.products[i].netto + '.00';
self.products[i].tax = parseInt(self.products[i].tax);
self.products[i].brutto = parseFloat(self.products[i].brutto);
self.products[i].rating = parseInt(self.products[i].rating);
};
});
}
$scope.product = $cookies.getObject('product') || {};
$scope.$watch('product', function() {
$cookies.putObject('product', $scope.product);
}, true);
this.addProduct = function() {
if(this.countCategories() >= 2) {
if(this.validateForm()) {
var product = {
id: this.products.length + 1,
name: $scope.product.name,
code: $scope.product.code,
image: $scope.product.image,
netto: this.intToFloat($scope.product.netto, 2),
tax: $scope.product.tax,
brutto: this.calculatePriceBr(),
rating: parseInt(this.ratingChecked()),
category: this.categoryChecked(),
option: this.optionChecked(),
selected: $scope.product.selected
};
this.products.push(product);
$scope.product = {};
$cookies.putObject('products', this.products);
$('#product-add').modal('hide');
return true;
} else {
return;
}
} else {
return;
}
};
})();
Hope this is what you are looking for.
I've made a little test based on your code (simplified):
var app = angular.module('myApp', ['ngCookies']);
app.controller('Store', Store);
Store.$inject = ['$scope', '$cookies', '$http'];
function Store($scope, $cookies, $http) {
var vm = this;
vm.products = [];
vm.cart = [] ;
vm.inCookie =[];
$http.get('products.xml').success(function(data) {
vm.products = data;
for (var i = 0; i < vm.products.length; i++) {
vm.products[i].id = parseInt(vm.products[i].id);
vm.products[i].netto = vm.products[i].netto + '.00';
vm.products[i].tax = parseInt(vm.products[i].tax);
vm.products[i].brutto = parseFloat(vm.products[i].brutto);
vm.products[i].rating = parseInt(vm.products[i].rating);
};
});
this.addProduct = function(row) {
vm.cart.push(row);
$cookies.put('cart', JSON.stringify(vm.cart));
vm.inCookie = JSON.parse($cookies.get('cart'));
};
}
You can see here how to add and retrieve data from a cookie.
In this plunkr you can see it working.
https://plnkr.co/edit/ew1ePjzbMxjAqtgx8hzq?p=preview
Hope this helps.
Regards!
I have created a access key Angular directive.
angular.module('tcne.common').directive("accessKey", function () {
return {
restrict: "A",
scope: {
},
link: function (scope, element, attrs) {
var $element = $(element);
$element.attr("accesskey", attrs.accessKey);
var content = $element.html();
for (var i = 0; i < content.length; i++) {
var char = content[i];
if (char.toLowerCase() === attrs.accessKey.toLowerCase()) {
content = content.substr(0, i) + "<u>" + char + "</u>" + content.substr(i + 1);
break;
}
}
$element.html(content);
},
replace: false
};
});
It underscores the access key in the button Label and adds the access key attribute to the element. Can I somehow prevent the accesskey from setting the button in focus? It kills the purpose of keyboard short cuts
edit: Rolled my own acccess key
angular.module('tcne.common').directive('accessKey', ['$compile', '$interval', function ($compile, $interval) {
var modifierPressed = false;
$("body").keyup(function (e) {
if (modifierPressed && !e.altKey) {
modifierPressed = false;
digestScopes();
}
});
$("body").keydown(function (e) {
modifierPressed = e.altKey;
if (modifierPressed && scopes.hasOwnProperty(String.fromCharCode(e.which).toLowerCase())) {
var scope = scopes[String.fromCharCode(e.which).toLowerCase()];
scope.handle();
return;
}
if (modifierPressed) {
e.preventDefault();
digestScopes();
}
});
function digestScopes() {
for (var index in scopes) {
if (scopes.hasOwnProperty(index)) {
var scope = scopes[index]
scope.$digest();
}
}
}
function isModifierPressed() {
return modifierPressed;
}
var scopes = {};
return {
restrict: 'A',
scope: {
},
link: function (scope, element, attrs) {
var key = attrs.accessKey.toLowerCase();
var content = element.html();
var char;
for (var i = 0; i < content.length; i++) {
char = content[i];
if (char.toLowerCase() === key) {
content = content.substr(0, i) + '<u><strong ng-if="highlight()">{{char}}</strong><span ng-if="!highlight()">{{char}}</span></u>' + content.substr(i + 1);
break;
}
}
element.html(content);
var underscoreScope = scope.$new();
underscoreScope.char = char;
underscoreScope.highlight = isModifierPressed;
underscoreScope.handle = element.click.bind(element);
scopes[key] = underscoreScope;
scope.$on('$destroy', function () {
delete scopes[key];
});
$compile(element.find("u"))(underscoreScope);
},
replace: false
};
}]);
It also highlights the access key button when alt key is pressed which is nice
Any pit falls with this code? Thanks
Found a pitfall, element.html and then $compile will break any directives inside the element that is already compiled. So I changed to
var captionElement = element.contents().first(":text");
var content = captionElement.text();
And then I add my custom content like
var view = $("<span>").html(content);
captionElement.replaceWith(view);
$compile(view)(vm);
Please let me know if this is considered bad practice
I am trying to clean up a controller that has too many lines of code in it. In the controller below where you find a function called getProductDetails, I would like to move the filter to a factory or a service, but I am not sure how to do it.
'use strict';
(function () {
var userQuoteBuild = angular.module('priceApp');
userQuoteBuild.controller('quoteBuilderController', function ($scope, $http) {
// loads of controller logic here...
$scope.getProductDetails = function (item) {
$scope.listOfProductVariants = item.default_variant_attributes;
// TODO: put this in its own factory?
$scope.selectedProductAttributes = $scope.listOfAttributes.filter(function (item) {
var validated = false, i, length = $scope.listOfProductVariants.length;
for (i = 0; i < length; i++) {
if (item.name === $scope.listOfProductVariants[i]){
validated = true;
}
}
return validated;
});
};
});
(function () {
'use strict';
angular
.module('priceApp')
.factory('filterService', filterService);
function filterService() {
var service = {
getValidated: getValidated
}
return service;
function getValidated(list, variants) {
return list.filter(function (item) {
var validated = false, i, length = variants.length;
for (i = 0; i < length; i++) {
if (item.name === variants[i]) {
validated = true;
}
}
return validated;
});
}
}
})();
Simply inject this filterService to your controller and then use it as in example here:
$scope.selectedProductAttributes = filterService
.getValidated($scope.listOfAttributes,
$scope.listOfProductVariants)
I followed John Papa's AngularJS Style Guide. Make sure to choose a better name than filterService. : )
Check this:
userQuoteBuild.factory('myService', function() {
var service = {
getProductDetails: function(item) {
// your logic
return value;
}
}
return service;
});
I'm learning AngularJs now, and trying to write my first directives.
So i have a question: is there any way to pass complex options to directive. For example i want to write directive wrapper for slick grid. It has a lot of options, columns for example, and it's imposible to configure it using attributes. Can i do simething like this?
<s-grid>
<s-grid-columns>
<s-grid-column id="title" title="Title"/>
<s-grid-column id="duration" title="Duration"/>
</s-grid-columns>
...
</s-grid>
And get all this properties as json object in s-grid directive?
So i could do it. Is it here any mistakes?
module
.directive('sGrid', [function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.columns = [];
this.setColumns = function(columns) {
$scope.columns = columns;
};
},
link: function (scope, element, attrs, controller, transclude) {
// for clearer present I initialize data right in directive
// start init data
var columns = scope.columns;
var options = {
enableCellNavigation: true,
enableColumnReorder: true
};
var data = [];
for (var i = 0; i < 50000; i++) {
var d = (data[i] = {});
d["id"] = "id_" + i;
d["num"] = i;
d["title"] = "Task " + i;
d["duration"] = "5 days";
d["percentComplete"] = Math.round(Math.random() * 100);
d["start"] = "01/01/2009";
d["finish"] = "01/05/2009";
d["effortDriven"] = (i % 5 == 0);
}
// end init data
// finally render layout
scope.grid = new Slick.Grid(element, data, columns, options);
$(window).resize(function () {
scope.grid.resizeCanvas();
})
}
}
}])
.directive("sGridColumns", function(){
return {
require: '^sGrid',
restrict: 'E',
controller: function($scope) {
var columns = $scope.columns = [];
this.addColumn = function(pane) {
columns.push(pane);
};
},
link: function (scope, element, attrs, gridCtrl){
gridCtrl.setColumns(scope.columns);
}
}
})
.directive('sGridColumn', function() {
return {
require: '^sGridColumns',
restrict: 'E',
transclude: 'element',
link: function (scope, element, attrs, gridCtrl) {
scope.field = scope.field || scope.id;
scope.title = scope.title || scope.field;
gridCtrl.addColumn({
id: attrs.id,
field: attrs.field || attrs.id,
name: attrs.name || attrs.field || attrs.id
});
}
};
});
And declaration:
<s-grid>
<s-grid-columns>
<s-grid-column id="title" name="Title"></s-grid-column>
<s-grid-column id="duration" name="Duration"></s-grid-column>
</s-grid-columns>
</s-grid>