Angular CSS Accordion Heading - css

I have an accordion that needs to change the color of it's heading background based on a value passed to it. e.g. red or green.
In the example below I have a header row for each store name. If a store is out of business, I need to flag the background of the heading as red instead of green. I am not able to get this to work.
<accordion id="accordion1" close-others="true">
<accordion-group is-open="isopen" ng-repeat="store in stores">
<accordion-heading class="container-fluid heading-highlight">
{{store.StoreName}}
</accordion-heading>
<form name="form">
<div class="form-row" ng-repeat="record in store.records">
<table>
<tr ng-formfield></tr> //dynamic form directive
</table>
</div>
</form>
</accordion-group>
</accordion>
I tried using the following directive but nothing works no matter what changes I make.
app.directive('headingHighlight', function () {
return {
restrict: 'A',
link: function ($scope, element, attrs, controller) {
$scope.$watch(element.children(), function () {
var children = element.children('.panel-heading');
for (var i = 0; i < children.length; i++) {
angular.element(children[i]).css('background', 'red');
}
});
}
};
});

working directive below:
app.directive('headingHighlight', function () {
return {
restrict: 'A',
link: function (scope, element, attrs, controller) {
scope.$watch(element, function () {
if (scope.color.Highlight != null) {
var panelhead = element.children('.panel-heading');
panelhead.css({
'background-image': '-webkit-linear-gradient(top,' + scope.color.Highlight +
' 0%, #e8e8e8 100%)', 'background-repeat': 'repeat-x', 'height': '85px;'
});
}
});
}
};
});

Related

Trouble using pseudo selector with a directive inside ng-repeat

I have a directive for a table with collapsible rows that only allows one row to be open at a time like this:
HTML:
<div class="my-table">
<div class="table-header">
... table headers ...
</div>
<my-table-row ng-repeat="itm in itms" itm="itm"></my-table-row>
</div>
JS Directive:
app.directive('myTable', function() {
return {
restrict: 'E',
scope: {
itms: '='
},
controller: 'TableController',
templateUrl: '/views/directives/my-table.html'
};
});
JS Controller:
app.controller('TableController', ['$scope', function($scope) {
$scope.rows = [];
$scope.toggleRow = function(row) {
row.open = !row.open;
};
this.addRow = function addRow(row) {
$scope.rows.push(row);
};
this.toggleOpen = function toggleOpen(selectedRow) {
angular.forEach($scope.rows, function(row) {
if (row === selectedRow) {
$scope.toggleRow(selectedRow);
} else {
row.open = false;
}
});
};
}]);
and the rows like this:
HTML:
<div class="table-row" ng-class="{ 'open': open }" ng-click="toggleOpen(this)">
... row contents code ...
</div>
JS Directive:
app.directive('myTableRow', function() {
return {
require: '^myTable',
restrict: 'E',
scope: {
itm: '='
},
link: function(scope, element, attrs, tableCtrl) {
scope.open = false;
scope.toggleOpen = tableCtrl.toggleOpen;
tableCtrl.addRow(scope);
},
templateUrl: '/views/directives/my-table-row.html'
};
});
used in template like this:
<my-table itms="itms"></my-table>
This all works, but I have a CSS pseudo element to round the corners of the final row like:
.table .table-row:last-child {
border-radius: 0 0 4px 4px;
}
However, ng-repeat is wrapping a tag around my table rows which is causing the pseudo selector to see them all as the last child. I've tried restructuring, tried using $last and making an actual class for the last row, moving things around, but I'm out of ideas. Any thoughts out there?
as I understood, css class table-row is located within myTableRow directive, which does not have replace: true property. This means that table-row css class is wrapped by my-table-row directive attribute, so, in order to get to the last row, your CSS rule should be:
.table my-table-row:last-child .table-row {
border-radius: 0 0 4px 4px;
}

Ember.js - CSS transition not always working

I'm using Ember.js with ember-cli and ember-data. Until now, development went quite smoothly but now I encountered an issue with css transitions which I can't solve myself.
I have a list. The list contains elements which have subelements. These subelements are also rendered as a list.
I fetch the data with ember-data from a REST API. After the data is received I want to fade in (css opacity) the list. But this does not work correctly. Sometime the transition is shown and sometimes not. I'm afraid it is a timing issue. So I added Ember.run.next to my code but it didn't help. When I add setTimeout with 1ms inside Ember.run.next it works like expected (at least on my computer). This feels quite weird. Here is my code I have so far. Every feedback appreciated.
controller.js:
export default Ember.Controller.extend({
//...
objects: []
//...
_pushToMatchings: function (response) {
var tempArray = [];
var pushed = false;
for (var i = 0, length = this.get('objects.length'); i < length; i++) {
pushed = false;
var match = this.get('objects').objectAt(i);
if (match.get('meta.items').objectAt(0) === response.get('meta.items').objectAt(0)) {
tempArray.pushObject(response);
pushed = true;
} else {
tempArray.pushObject(match);
}
}
if (!pushed) {
tempArray.pushObject(response);
}
this.set('objects', tempArray);
},
fetch: function() {
var self = this;
// find parent item
this.get('store').find('item', id).then(function (item) {
self._pushToMatchings(Ember.Object.create({
meta: {
items: [id],
isLoading: true,
label: item.get('label')
},
content: []
}));
self.set('isOpen', true);
// child object
self.get('store').find('child', searchParams).then(function (result) {
(function (resultPtr) {
Ember.run.next(function () {
setTimeout(function () { // #todo why do we need timeout here? whitout there is no fade out with opacity in css possible
resultPtr.set('meta.isLoaded', true);
}, 1); // 1 is enough but give spinner some time otherwise it looks ugly
});
}(result));
result.set('meta.label', item.get('label'));
self._pushToMatchings(result);
}, function (error) { /* ... */ });
}, function (error) { /* ... */ });
}
}
controller.hbs:
<div>
{{item-list close="close" elements=objects }}
</div>
item-list.js
export default Ember.Component.extend({
elements: [],
actions: {
close: function () {
this.sendAction('close');
}
}
});
item-list.hbs
<div class="items-list__buttons">
<i class="icon-close_32" {{action "close" }}></i>
</div>
<div class="items-list__content">
{{#each matching in elements}}
<div class="items-list__item">
<h2>{{t "items.offers" }} {{matching.meta.label}}</h2>
{{spinner-element hideEvent=matching.meta.isLoaded }}
<div {{bind-attr class=":items-list__box matching.meta.isLoaded:items--fadeIn" }}>
{{#each item in matching.content}}
<div>
<!-- Render details of item -->
</div>
{{/each}}
</div>
</div>
{{/each}}
</div>
CSS:
.items-list__box {
opacity: 0;
transition: opacity 150ms ease 100ms;
}
.items--fadeIn {
opacity: 1;
}
You can use Ember.run.later, works same way than setTimeout.
Ember.run.later(this ,function(){
resultPtr.set('meta.isLoaded', true);
}, 100);
I'm not sure but this is neccesary because the div would be render with the class "items--fadeIn" that the transition wouldn't occur. I've done this way and worked for me, just try incrementing the time a little.
I know this is a late answer, but for others receiving a similar issue:
Your problem is that Ember is re-rendering your entire list of items in your {{#each because every time something changes you are giving it an entirely new array of objects, instead of changing the properties of the objects in the array. What you need to do is to define your array of objects and manipulate their properties so that only the objects that change get re-rendered.

How do I add input controls dynamically using Meteor?

I have a form in weather that would have had the condition User add as many lines he needs. He clicks a button and an input is added below the other.
I can do this using jQuery, but I would prefer to use the resources of Meteor. Is it possible to do?
Yes it is, here is an example from one of my apps using the underscore package
In the main template:
<template name="ask">
{{#each answerArray}}
{{>answer}}
{{/each}}
<button id="addItem">Add item</button>
</template>
<template name="answer">
<div class="input-group pt10">
<input class="form-control answer" maxlength="30" placeholder="Answer (max 30 chars)" name="answer" />
<span class="input-group-btn">
<button class="btn btn-danger delButton" id="{{id}}" data-id="{{id}}" type="button">Delete</button>
</span>
</div>
</template>
In the js file:
Template.ask.created = function () {
Session.set('action', 'ask');
answerArray = [ //adding at least two items but it could also be empty
{
id: Random.id(), //using this to give an unique id to the control
value: ''
},
{
id: Random.id(),
value: ''
}
];
Session.set('answerArr', answerArray);
}
And the click event:
Template.ask.events = {
'click #addItem': function () {
var answerArray = Session.get('answerArr');
answerArray.push({
id: Random.id() //just a placeholder, you could put any here
});
Session.set('answerArr', answerArray);
}
}
And finally the helper:
Template.ask.helpers({
answerArray: function () {
var answerArray = Session.get("answerArr")
while (answerArray.length < 2) { //i chose to have it between 2 and 6, you can remove these
answerArray.push({
id: Random.id()
})
}
while (answerArray.length > 6) { // maximum
answerArray.pop();
}
Session.set('answerArr', answerArray);
return answerArray;
}
}
This will reactively increase the number of inputs. After that, if you want to process the inputs you could do the following, on a submit form event or button click:
'click #saveQ': function (e) {
e.preventDefault();
var arr = [];
_.each($('.answer'), function (item) {
if ($(item).val() != '')
arr.push({
answer: $(item).val(), //this you customize for your own purposes
number: 0
})
});
And also if you want to delete an input from the page you can use:
Template.answer.events = {
'click .delButton': function (e) {
var thisId = $(e.target).attr("id");
var answerArray = Session.get('answerArr');
var filteredArray = _.filter(answerArray, function (item) {
return item.id != thisId;
});
Session.set('answerArr', filteredArray);
}
}

How should I bind to an element's style in an angular directive?

I would like to bind to an absolutely positioned element's top style in a directive. Is this possible?
Here is what I would like to do in made up code:
angular.module('exampleModule').directive('resize', [function () {
return {
link: function(scope, iElement, iAttrs) {
var top = 14;
// There is no styleChange event
iElement.bind('styleChange', styleChangeHandler);
function styleChangeHandler(event) {
if(event.style == 'top' && event.value != top) {
scope.$apply(function(scope){
scope[iAttrs.topChanged](event.value);
});
}
}
}
}
}]);
There are no style change events. If you are in control of the style changing you can create your custom event and trigger this manually. Or you could create a watch function, something like this:
link: function(scope, iElement, iAttrs) {
//...
scope.$watch(function(){
return iElement.css('top');
},styleChangeFn,true);
function styleChangeFn(value,old){
if(value !== old)
scope[iAttrs.topChanged](value);
}
}
So here is what I came up with (greatly helped by joakimbl's answer). It will work for watching any style.
The directive:
angular.module('unwalked.directives').directive('watchStyle', [function () {
return {
link: function(scope, iElement, iAttrs) {
scope.$watch(function(){
return iElement.css(iAttrs['watchedStyle']);
},
styleChanged,
true);
function styleChanged(newValue, oldValue) {
if(newValue !== oldValue) {
scope[iAttrs['watchStyle']](newValue);
}
}
}
};
}]);
Usage (Note: no brackets on the callback - it's just the function name):
<div watch-style="functionOnController" watched-style="height" >

MVC Twitter Bootstrap unobtrusive error handling

I've been trying to get MVC Jquery unobtrusive error handling working with twitter bootstrap for some time now. Its got to the point were i'm either going to edit jquery.validate or do some hack and slash on document.ready.
In order to get unobtrusive error handling to work with Bootstrap and MVC I need to make it so the 'error' class it appended to the 'control-group' class. As well as that, the 'error' class is appended to the input.
I was wondering if anyone in the community has already found a solution.
For example
Typical bootstrap markup would be like so...
<div class="control-group">
<label for="Username">Username</label>
<div class="controls">
<input data-val="true" data-val-required="Username is required" id="Username" name="Username" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Username" data-valmsg-replace="true"></span>
</div>
</div>
What should happen, on blur when jquery.validate unobtrusive fires... it would change to the following
<div class="control-group error">
<label for="Username">Username</label>
<div class="controls">
<input data-val="true" data-val-required="Username is required" id="Username" name="Username" type="text" value="" />
<span class="field-validation-valid help-inline" data-valmsg-for="Username" data-valmsg-replace="true"></span>
</div>
</div>
To get this to work on postback/submit you can do the following...
//twitter bootstrap on post
$(function () {
$('span.field-validation-valid, span.field-validation-error').each(function () {
$(this).addClass('help-inline');
});
$('form').submit(function () {
if ($(this).valid()) {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length == 0) {
$(this).removeClass('error');
}
});
}
else {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
}
});
$('form').each(function () {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
});
});
However, on blur it won't work as you'd expect. I don't want to edit the bootstrap CSS, or Jquery.validate files as they will likely roll out an update at some-point.
Would I create a delegate, or a bind to the jquery functions and work from there. This is deep JS code which I'm not familiar with but could with time firefight my way through it.
Does any one know where I'd start with this problem, or know where it is implemented/been discussed?
var page = function () {
//Update that validator
$.validator.setDefaults({
highlight: function (element) {
$(element).closest(".control-group").addClass("error");
},
unhighlight: function (element) {
$(element).closest(".control-group").removeClass("error");
}
});
} ();
Finally, this fixed it for me. I hope this helps other people too...
My final JS ended like so.
$(function () {
$('span.field-validation-valid, span.field-validation-error').each(function () {
$(this).addClass('help-inline');
});
$('form').submit(function () {
if ($(this).valid()) {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length == 0) {
$(this).removeClass('error');
}
});
}
else {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
}
});
$('form').each(function () {
$(this).find('div.control-group').each(function () {
if ($(this).find('span.field-validation-error').length > 0) {
$(this).addClass('error');
}
});
});
});
var page = function () {
//Update that validator
$.validator.setDefaults({
highlight: function (element) {
$(element).closest(".control-group").addClass("error");
},
unhighlight: function (element) {
$(element).closest(".control-group").removeClass("error");
}
});
} ();
Here's a nice solution...
Add this to your _Layout.cshtml file outside jQuery(document).ready():
<script type="text/javascript">
jQuery.validator.setDefaults({
highlight: function (element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).addClass(errorClass).removeClass(validClass);
} else {
$(element).addClass(errorClass).removeClass(validClass);
$(element).closest('.control-group').removeClass('success').addClass('error');
}
},
unhighlight: function (element, errorClass, validClass) {
if (element.type === 'radio') {
this.findByName(element.name).removeClass(errorClass).addClass(validClass);
} else {
$(element).removeClass(errorClass).addClass(validClass);
$(element).closest('.control-group').removeClass('error').addClass('success');
}
}
});
</script>
Add this inside $(document).ready():
$("span.field-validation-valid, span.field-validation-error").addClass('help-inline');
$("div.control-group").has("span.field-validation-error").addClass('error');
$("div.validation-summary-errors").has("li:visible").addClass("alert alert-block alert-error");
You're good to go.
Code pieces taken from:
Twitter Bootstrap validation styles with ASP.NET MVC
Integrating Bootstrap Error styling with MVC’s Unobtrusive Error Validation
#daveb's answer
In addition to the answer provided by #leniel-macaferi I use the following as my $(document).ready() function:
$(function () {
$("span.field-validation-valid, span.field-validation-error").addClass('help-inline');
$("div.control-group").has("span.field-validation-error").addClass('error');
$("div.validation-summary-errors").has("li:visible").addClass("alert alert-block alert-error");
});
This also sets the "error" class on the control group if server side validation has failed on a form post and formats any validation summary nicely as a bootstrap error alert.
I know this is an oldy, but I thought I'd share my answer to update for Bootstrap 3. I scratched my head for quite sometime, before building on top of the solution given by Leniel Macaferi.
On top of changing the clases to reflect Bootstrap 3, I thought it would be a nice touch to present the user with a glyphicon to represent the state of the field.
(function ($) {
var defaultOptions = {
errorClass: 'has-error has-feedback',
validClass: 'has-success has-feedback',
highlight: function (element, errorClass, validClass) {
var _formGroup = $(element).closest(".form-group");
_formGroup
.addClass('has-error')
.removeClass('has-success');
if (!_formGroup.hasClass("has-feedback")) {
_formGroup.addClass("has-feedback");
}
var _feedbackIcon = $(element).closest(".form-group").find(".glyphicon");
if (_feedbackIcon.length) {
$(_feedbackIcon)
.removeClass("glyphicon-ok")
.removeClass("glyphicon-remove")
.addClass("glyphicon-remove");
}
else {
$("<span class='glyphicon glyphicon-remove form-control-feedback' aria-hidden='true'></span>")
.insertAfter(element);
}
},
unhighlight: function (element, errorClass, validClass) {
var _formGroup = $(element).closest(".form-group");
_formGroup
.removeClass('has-error')
.addClass('has-success');
if (!_formGroup.hasClass("has-feedback")) {
_formGroup.addClass("has-feedback");
}
var _feedbackIcon = $(element).closest(".form-group").find(".glyphicon");
if (_feedbackIcon.length) {
$(_feedbackIcon)
.removeClass("glyphicon-ok")
.removeClass("glyphicon-remove")
.addClass("glyphicon-ok");
}
else {
$("<span class='glyphicon glyphicon-ok form-control-feedback' aria-hidden='true'></span>")
.insertAfter(element);
}
}
};
$.validator.setDefaults(defaultOptions);
$.validator.unobtrusive.options = {
errorClass: defaultOptions.errorClass,
validClass: defaultOptions.validClass,
};
})(jQuery);
Try use this plugin I've made https://github.com/sandrocaseiro/jquery.validate.unobtrusive.bootstrap
What I did differently from the others answers was to override the errorPlacement and success methods from validate.unobtrusive with my own implementations, but without removing the original implementation so nothing will break.
My implementation look like this:
erroPlacement:
function onError(formElement, error, inputElement) {
var container = $(formElement).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
//calling original validate.unobtrusive method
errorPlacementBase(error, inputElement);
if (replace) {
var group = inputElement.parent();
if (group.hasClass('form-group')) {
group.addClass('has-error').removeClass('has-success');
}
group = group.parent();
if (group.hasClass('form-group')) {
group.addClass('has-error').removeClass('has-success');
}
}
}
success:
function onSuccess(error) {
var container = error.data("unobtrusiveContainer");
//calling original validate.unobtrusive method
successBase(error);
if (container) {
var group = container.parent();
if (group.hasClass('form-group')) {
group.addClass('has-success').removeClass('has-error');
}
group = group.parent();
if (group.hasClass('form-group')) {
group.addClass('has-success').removeClass('has-error');
}
}
}
Out of the box I wanted on blur to raise my error validation. I found this wasn't the case with Jquery Unobtrusive. It seemed to work if you had a select input but not on a text type input. To get around this for me, perhaps its clumsy but I used the following.
$(function () {
$("input[type='text']").blur(function () {
$('form').validate().element(this);
});
});
You can change it is just enabled on certain inputs that have a specific css class.
$(function () {
$(".EnableOnblurUnobtrusiveValidation").blur(function () {
$('form').validate().element(this);
});
});
EnableOnblurUnobtrusiveValidation... is a bit of a long name but you get the jist.
Use TwitterBootstrapMvc.
It takes care of unobtrusive validation attributes automatically and all you have to write to get a full control group with label, input and validation is:
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Field)
Good luck!

Resources