I have a div to visualize progress.
Therefor I have this ng-style definition:
ng-style="{'background-image':'linear-gradient(left, rgba(255,0,0,1)'+bufferProgressPercent!=='NaN'?bufferProgressPercent:0+'%,rgba(0,0,0,0)'+(bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%)!important'};"
//output in developer-tools
<div class="audio-controls-wrapper" ng-style="{'backgroundImage':'linear- gradient(left, rgba(255,0,0,1)'+bufferProgressPercent!=='NaN'? bufferProgressPercent:0+'%,rgba(0,0,0,0)'+(bufferProgressPercent!=='NaN'? bufferProgressPercent:0)+'%)'};">
The document only shows the as clear-text. The values don't get rendered.
The values are correct:
{{'background-image:linear-gradient(left, rgba(255,0,0,1)'+ (bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%,rgba(0,0,0,0)'+(bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%)'}}
Gives this out:
background-image:linear-gradient(left, rgba(255,0,0,1)59%,rgba(0,0,0,0)59%)
Another attempt was to create a directive:
<div class="audio-controls-wrapper" progress-animation="bufferProgressPercent" >
Directive:
scope.$watch('progressAnimation', function(current, old){
if(angular.isDefined(current) && current !== old){
var backgroundImage = 'linear-gradient(left, rgba(255,0,0,1)'+ (current!=='NaN'?current:0)+'%,rgba(0,0,0,0)'+(current!=='NaN'? current:0)+'%)!important';
//scope.$applyAsync(function(){
//element.css({'backgroundImage':backgroundImage});
element[0].style.backgroundImage = backgroundImage;
$compile(element.contents())(scope);
//});
console.log(backgroundImage)
console.log(element[0].style)
}
});
But the attribute backgroundImage of this element is never set.
Did you enter the watch function into directive ?
for example
.directive('progressAnimation', function () {
return {
restrict: 'A', //E = element, A = attribute, C = class, M = comment
scope: { // input the var that you want to watch
},
link: function ($scope, element, attrs) {
//put your watch function here
if(angular.isDefined(current) && current !== old){
var backgroundImage = 'linear-gradient(left, rgba(255,0,0,1)'+ (current!=='NaN'?current:0)+'%,rgba(0,0,0,0)'+(current!=='NaN'? current:0)+'%)!important';
element[0].style.backgroundImage = backgroundImage;
$compile(element.contents())(scope);
console.log(backgroundImage)
console.log(element[0].style)
}
} //DOM manipulation
}
});
To set the background in I had to use
background
instead of
background-image
further I had to replace
linear-gradient(...
with
-moz-linear-gradient(
that means
{{'background-image:linear-gradient(left, rgba(255,0,0,1)'+ (bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%,rgba(0,0,0,0)'+(bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%)'}}
became this:
{{'background:-moz-linear-gradient((left, rgba(255,0,0,1)'+ (bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%,rgba(0,0,0,0)'+(bufferProgressPercent!=='NaN'?bufferProgressPercent:0)+'%)'}}
I guess I have to add all the browser besides chrom a certain linear gradient.
Related
I'm trying to create an editor which does "syntax highlighting",
it is rather simple:
yellow -> <span style="color:yellow">yellow</span>
I'm also using <code contenteditable> html5 tag to replace <textarea>, and have color output.
I started from angularjs documentation, and created the following simple directive. It does work, except it do not update the contenteditable area with the generated html.
If I use a element.html(htmlTrusted) instead of ngModel.$setViewValue(htmlTrusted), everything works, except the cursor jumps to the beginning at each keypress.
directive:
app.directive("contenteditable", function($sce) {
return {
restrict: "A", // only activate on element attribute
require: "?ngModel", // get ng-model, if not provided in html, then null
link: function(scope, element, attrs, ngModel) {
if (!ngModel) {return;} // do nothing if no ng-model
element.on('blur keyup change', function() {
console.log('app.directive->contenteditable->link->element.on()');
//runs at each event inside <div contenteditable>
scope.$evalAsync(read);
});
function read() {
console.log('app.directive->contenteditable->link->read()');
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if ( attrs.stripBr && html == '<br>' ) {
html = '';
}
html = html.replace(/</, '<');
html = html.replace(/>/, '>');
html = html.replace(/<span\ style=\"color:\w+\">(.*?)<\/span>/g, "$1");
html = html.replace('yellow', '<span style="color:yellow">yellow</span>');
html = html.replace('green', '<span style="color:green">green</span>');
html = html.replace('purple', '<span style="color:purple">purple</span>');
html = html.replace('blue', '<span style="color:yellow">blue</span>');
console.log('read()-> html:', html);
var htmlTrusted = $sce.trustAsHtml(html);
ngModel.$setViewValue(htmlTrusted);
}
read(); // INITIALIZATION, run read() when initializing
}
};
});
html:
<body ng-app="MyApp">
<code contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>This <span style="color:purple">text is purple.</span> Change me!</code>
<hr>
<pre>{{userContent}}</pre>
</body>
plunkr: demo (type yellow, green or blue into the change me input area)
I tried scope.$apply(), ngModel.$render() but has no effect. I must miss something really obvious...
The links I already read through:
others' plunker demo 1
others' plunker demo 2
angularjs documentation's example
$sce.trustAsHtml stackoverflow question
setViewValue stackoverflow question
setViewValue not updating stackoverflow question
Any help is much appreciated. Please see the plunker demo above.
After almost a year, I finally settled to Codemirror, and I was never happier.
I'm doing side-by-side markdown source editing with live update (with syntax highlighting, so even a bit more advanced than stackoverflow's editing page.)
I created a simple codeEditor angular directive, which requires codeMirror, and uses it.
For completeness, here is the component sourcecode:
$ cat components/codeEditor/code-editor.html
<div class="code-editor"></div>
$ cat codeEditor.js
'use strict';
angular.module('myApp')
.directive('codeEditor', function($timeout, TextUtils){
return {
restrict: 'E',
replace: true,
require: '?ngModel',
transclude: true,
scope: {
syntax: '#',
theme: '#'
},
templateUrl: 'components/codeEditor/code-editor.html',
link: function(scope, element, attrs, ngModelCtrl, transclude){
// Initialize Codemirror
var option = {
mode: scope.syntax || 'xml',
theme: scope.theme || 'default',
lineNumbers: true
};
if (option.mode === 'xml') {
option.htmlMode = true;
}
scope.$on('toedit', function () { //event
//This is required to correctly refresh the codemirror view.
// otherwise the view stuck with 'Both <code...empty.' initial text.
$timeout(function() {
editor.refresh();
});
});
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('codeEditor.js needs CodeMirror to work... (o rly?)');
}
var editor = window.CodeMirror(element[0], option);
// Handle setting the editor when the model changes if ngModel exists
if(ngModelCtrl) {
// Timeout is required here to give ngModel a chance to setup. This prevents
// a value of undefined getting passed as the view is rendered for the first
// time, which causes CodeMirror to throw an error.
$timeout(function(){
ngModelCtrl.$render = function() {
if (!!ngModelCtrl.$viewValue) {
// overwrite <code-editor>SOMETHING</code-editor>
// if the $scope.content.code (ngModelCtrl.$viewValue) is not empty.
editor.setValue(ngModelCtrl.$viewValue); //THIRD happening
}
};
ngModelCtrl.$render();
});
}
transclude(scope, function(clonedEl){
var initialText = clonedEl.text();
if (!!initialText) {
initialText = TextUtils.normalizeWhitespace(initialText);
} else {
initialText = 'Both <code-editor> tag and $scope.content.code is empty.';
}
editor.setValue(initialText); // FIRST happening
// Handle setting the model if ngModel exists
if(ngModelCtrl){
// Wrap these initial setting calls in a $timeout to give angular a chance
// to setup the view and set any initial model values that may be set in the view
$timeout(function(){
// Populate the initial ng-model if it exists and is empty.
// Prioritize the value in ngModel.
if(initialText && !ngModelCtrl.$viewValue){
ngModelCtrl.$setViewValue(initialText); //SECOND happening
}
// Whenever the editor emits any change events, update the value
// of the model.
editor.on('change', function(){
ngModelCtrl.$setViewValue(editor.getValue());
});
});
}
});
// Clean up the CodeMirror change event whenever the directive is destroyed
scope.$on('$destroy', function(){
editor.off('change');
});
}
};
});
There is also inside the components/codeEditor/vendor directory the full codemirror sourcecode.
I can highly recommend codeMirror. It is a rocksolid component, works in
every browser combination (firefox, firefox for android, chromium).
I have a list of item with two iterations. I want a sticky title when the title scroll up from the view area. I have done it with jquery, but can't able to do in angular. Created a fiddle https://jsfiddle.net/1vf5ska7/
I just to want to add a class in tag when the title is goes up to the view area.
angular.element(document.querySelector('#l-content__desc__split1__body')).on('scroll', function() {
});
And the important thing is it is not a window scroll. It's a div scroll
Please help me.
Thanks..
You need to include a directive and operate on it. If $window.pageYOffset is greater than the position of the element you apply a specific class to that element which is positioned fixed.
var app = angular.module('app', []);
app.directive('setClassOnTop', function ($window) {
var $win = angular.element($window); // wrap window object as jQuery object
return {
restrict: 'A',
link: function (scope, element, attrs) {
var title = angular.element(document.getElementById('sticky-title'));
var offsetTop = title[0].offsetTop;
$win.on('scroll', function (e) {
if ($window.pageYOffset > offsetTop) {
angular.element(title[0]).addClass('floating-title');
} else {
angular.element(title[0]).removeClass('floating-title');
}
});
}
};
});
And here is the updated fiddle:
https://jsfiddle.net/1vf5ska7/3/
Say I've got a <nav> element with three buttons, and an <article> containing three <section> elements. I want a user to be able to click a button, which toggles the display of all of the <section> elements in such a way that only the one relevant <section> is shown.
I'm new to AngularJS and trying to figure out if theres a more Angularly way of achieving this than by giving the nav elements ng-click attributes and the section elements the ng-show attribute. It feels very imperative.
My HTML would look something like this:
<nav>
<div ng-click="showRedSection()"></div>
<div ng-click="showBlueSection()"></div> //etc.
<nav>
<article>
<section ng-show="redSectionShown">
// content; etc.
</section>
//etc.
</article>
Am I on the right track, or is there an approach that makes this more declarative?
What you are going is correct.
Instead of needing a function to toggle the value on click you could do redSectionShown = !!redSectionShown
Or if you have a lot of sections and other data you want to store create a service to store the visible state of the regions and create directive that can toggle the values or use the values to hide elements.
The second approach reduces your $scope pollution.
.service('ViewableRegions', function() {
var viewablePropertiesMap = {};
this.getValue = function(value) {
return viewablePropertiesMap[value];
}
this.setValue = function(valueToUpdate, value) {
viewablePropertiesMap[valueToUpdate] = value
}
})
Directive to toggle region visability
.directive('regionToggler', function(ViewableRegions) {
return {
restrict: 'A',
compile: function($element, $attrs) {
var directiveName = this.name;
var valueToUpdate = attrs[directiveName];
return function(scope, element, attrs) {
element.on('click', function() {
var currentValue = ViewableRegions.getValue(ViewableRegions);
ViewableRegions.setValue(valueToUpdate, !!currentValue);
scope.$apply();
})
};
}
};
}
);
Directive to display the regions
.directive('regionDisplayer', function(ViewableRegions) {
return {
restrict: 'A',
compile: function($element, $attrs) {
var directiveName = this.name;
var valueToUpdate = attrs[directiveName];
return function(scope, element, attrs) {
scope.$watch(
function() {
return ViewableRegions.getValue(ViewableRegions);
},
function(newVal) {
if (newVal) {
element[0].style.display = "none";
} else {
element[0].style.display = "";
}
}
)
};
}
};
}
);
HTML Uasge
//etc.
<article>
<section region-displayer="redSectionShown">
// content; etc.
</section>
//etc.
</article>
I would actually recommend trying to use ng-hide as opposed to ng-show. These directives complete the same task most of the time,but ng-hide defaults to a hidden state, where you can then setup parameters in which the information will be shown. I ran into a similar problem with an app I was working on in angular, and using ng-hide seemed to help simplify the code in my view.
You could try something like:
<div ng-click="sectionToShow=1"></div>
<div ng-click="sectionToShow=2"></div>
<article>
<section ng-show="sectionToShow==1"></section>
</article>
I haven't tested this, but there's no reason it shouldn't work. That way, you will automatically only ever show the correct section, and all other sections will auto hide themselves.
I have two controllers and share data between them with an app.factory function.
The first controller adds a widget in the model array (pluginsDisplayed) when a link is clicked. The widget is pushed into the array and this change is reflected into the view (that uses ng-repeat to show the array content):
<div ng-repeat="pluginD in pluginsDisplayed">
<div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
</div>
The widget is built upon three directives, k2plugin, remove and resize. The remove directive adds a span to the template of the k2plugin directive. When said span is clicked, the right element into the shared array is deleted with Array.splice(). The shared array is correctly updated, but the change is not reflected in the view. However, when another element is added, after the remove, the view is refreshed correctly and the previously-deleted element is not shown.
What am I getting wrong? Could you explain me why this doesn't work?
Is there a better way to do what I'm trying to do with AngularJS?
This is my index.html:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js">
</script>
<script src="main.js"></script>
</head>
<body>
<div ng-app="livePlugins">
<div ng-controller="pluginlistctrl">
<span>Add one of {{pluginList.length}} plugins</span>
<li ng-repeat="plugin in pluginList">
<span>{{plugin.name}}</span>
</li>
</div>
<div ng-controller="k2ctrl">
<div ng-repeat="pluginD in pluginsDisplayed">
<div k2plugin pluginname="{{pluginD.name}}" pluginid="{{pluginD.id}}"></div>
</div>
</div>
</div>
</body>
</html>
This is my main.js:
var app = angular.module ("livePlugins",[]);
app.factory('Data', function () {
return {pluginsDisplayed: []};
});
app.controller ("pluginlistctrl", function ($scope, Data) {
$scope.pluginList = [{name: "plugin1"}, {name:"plugin2"}, {name:"plugin3"}];
$scope.add = function () {
console.log ("Called add on", this.plugin.name, this.pluginList);
var newPlugin = {};
newPlugin.id = this.plugin.name + '_' + (new Date()).getTime();
newPlugin.name = this.plugin.name;
Data.pluginsDisplayed.push (newPlugin);
}
})
app.controller ("k2ctrl", function ($scope, Data) {
$scope.pluginsDisplayed = Data.pluginsDisplayed;
$scope.remove = function (element) {
console.log ("Called remove on ", this.pluginid, element);
var len = $scope.pluginsDisplayed.length;
var index = -1;
// Find the element in the array
for (var i = 0; i < len; i += 1) {
if ($scope.pluginsDisplayed[i].id === this.pluginid) {
index = i;
break;
}
}
// Remove the element
if (index !== -1) {
console.log ("removing the element from the array, index: ", index);
$scope.pluginsDisplayed.splice(index,1);
}
}
$scope.resize = function () {
console.log ("Called resize on ", this.pluginid);
}
})
app.directive("k2plugin", function () {
return {
restrict: "A",
scope: true,
link: function (scope, elements, attrs) {
console.log ("creating plugin");
// This won't work immediately. Attribute pluginname will be undefined
// as soon as this is called.
scope.pluginname = "Loading...";
scope.pluginid = attrs.pluginid;
// Observe changes to interpolated attribute
attrs.$observe('pluginname', function(value) {
console.log('pluginname has changed value to ' + value);
scope.pluginname = attrs.pluginname;
});
// Observe changes to interpolated attribute
attrs.$observe('pluginid', function(value) {
console.log('pluginid has changed value to ' + value);
scope.pluginid = attrs.pluginid;
});
},
template: "<div>{{pluginname}} <span resize>_</span> <span remove>X</span>" +
"<div>Plugin DIV</div>" +
"</div>",
replace: true
};
});
app.directive("remove", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.remove(element);
})
};
});
app.directive("resize", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.resize(element);
})
};
});
Whenever you do some form of operation outside of AngularJS, such as doing an Ajax call with jQuery, or binding an event to an element like you have here you need to let AngularJS know to update itself. Here is the code change you need to do:
app.directive("remove", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.remove(element);
scope.$apply();
})
};
});
app.directive("resize", function () {
return function (scope, element, attrs) {
element.bind ("mousedown", function () {
scope.resize(element);
scope.$apply();
})
};
});
Here is the documentation on it: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
If you add a $scope.$apply(); right after $scope.pluginsDisplayed.splice(index,1); then it works.
I am not sure why this is happening, but basically when AngularJS doesn't know that the $scope has changed, it requires to call $apply manually. I am also new to AngularJS so cannot explain this better. I need too look more into it.
I found this awesome article that explains it quite properly.
Note: I think it might be better to use ng-click (docs) rather than binding to "mousedown". I wrote a simple app here (http://avinash.me/losh, source http://github.com/hardfire/losh) based on AngularJS. It is not very clean, but it might be of help.
I had the same issue. The problem was because 'ng-controller' was defined twice (in routing and also in the HTML).
Remove "track by index" from the ng-repeat and it would refresh the DOM
There's an easy way to do that. Very easy. Since I noticed that
$scope.yourModel = [];
removes all $scope.yourModel array list you can do like this
function deleteAnObjectByKey(objects, key) {
var clonedObjects = Object.assign({}, objects);
for (var x in clonedObjects)
if (clonedObjects.hasOwnProperty(x))
if (clonedObjects[x].id == key)
delete clonedObjects[x];
$scope.yourModel = clonedObjects;
}
The $scope.yourModel will be updated with the clonedObjects.
Hope that helps.
For an only visual editor I'm trying to create a new directive that writes a CSS style. I'm stuck at trying to get the directive to update when a checkbox is clicked to make the background-color property transparent.
Here's my (non-working) directive:
myApp.directive('customstyle', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var bgColor;
scope.$watch(attrs.myTransparent, function (value) {
if (value) {
bgColor = 'transparent';
} else {
bgColor = attrs.myBgcolor;
}
updateStyle();
}, true);
function updateStyle() {
var htmlText = '<style>.' + attrs.myClass + '{';
htmlText += 'background-color: ' + bgColor + ';';
htmlText += "}</style>";
element.replaceWith(htmlText);
}
updateStyle();
}
}
});
and html element:
<customstyle my-class="examplediv" my-transparent="settings.Window.Transparent" my-bgcolor="settings.Window.BackgroundColor"></customstyle>
Here's a jsfiddle of the situation: http://jsfiddle.net/psinke/jYQc6/
Any help would be greatly appreciated.
Try using the directive directly on the element you want to change, it's easier to do and to maintain.
HTML:
<div class="examplediv customstyle"
my-transparent="settings.Window.Transparent"
my-bgcolor="{{settings.Window.BackgroundColor}}">
</div>
Note: Use {{settings.Window.BackgroundColor}} to pass the property's value and not a String.
Directive:
myApp.directive('customstyle', function () {
return {
restrict: 'AC',
link: function (scope, element, attrs) {
scope.$watch(attrs.myTransparent, function (value) {
element.css('background-color', (value ? 'transparent' : attrs.myBgcolor));
});
}
}
});
Note: Use element.css() to change CSS properties directly on the element.
jsFiddler: http://jsfiddle.net/jYQc6/8/
I was having the same problem and using bmleite's solution solved it. I had a custom element with a custom attribute very similar to the one above, and changing the directive to be applied on a regular DIV instead of the custom attribute fixed it for me.
In my solution I also have the following line of code right after the element has been modified:
$compile(element.contents())(scope);
Remember to inject the $compile service in the directive function declaration:
myApp.directive('directiveName', function ($compile) { ...
Thanks for a great post!