how to change a css class when all tabs are closed in ui.bootstrap.accordion - angular-ui-bootstrap

How can I change a css class when all the tabs in ui.bootstrap.accordion are closed,for instance if the tabs were closed because another tab triggered them to close, the ng-class expression won't run and won't change the icon.
This is the view
<uib-accordion>
<uib-accordion-group ng-repeat="item in portafolio | orderBy : 'order' " ng-click="item.opened=!item.opened">
<uib-accordion-heading>
<i class="fa fa-lg" ng-class="(item.opened === true) ? 'fa-minus-circle':'fa-plus-circle'"></i>
<span>{{item.name}}</span>
<i class="pull-right fa fa-lg" ng-class="{'fa-caret-down': true === item.opened, 'fa-caret-right': false === item.opened}"></i>
</uib-accordion-heading>
<div ng-include="'modules/widgets/portafolio/tpl/'+item.type+'.html'"></div>
</uib-accordion-group>
</uib-accordion>
And in the controller I have this.
$scope.portfolio = [
{ type: 'Balance', order:1, name:'current balance', opened: false},
{ type: 'Credits', order:2, name:'Credits', opened: false},
{ type: 'Investments', order:3, name:'Investments', opened: false}
];
I tried to use the ng-class directive in order to evaluate if the item.opened was true or false,but when the tabs were closed because another tab closed them, the item.opened wont update.
Indeed, I've researched that with $watch I can "watch" for changes and run a function, I saw that when I click on one tab, a css class '.panel-open'is added ,but how can I watch that div element that the css class has been added in order to update the i class elements.
Thanks.

Well, you basically want to watch for any changes in your portfolios array, when all of the opened properties are false, trigger a value bound to $scope that will change a class, preferably using something like ng-class (this will also work using ng-style).
Because you watch an array, you should using $watchCollection instead of plain old $watch
$scope.$watchCollection(function() {
return $scope.portfolio.every((item) => {
return item.opened === false;
})
}, function(newValue, oldValue) {
$scope.value = newValue;
})
Array.prototype.every will return true if all the items in an array return true on a predicate function, in this case, all of the .opened properties are false.
Then setup something like
ng-class="{'btn-primary': value, 'btn-success': !value}"
Here's a plunker
Your question was kind of hard to understand, comment if I didn't cover something.

Related

Best practice for html tags in vue-i18n translations?

I am using vue-i18n and I need to translate a sentence with an anchor tag in the middle. Obviously I want to keep the html specific markup out of my translations, but how best to handle this?
Consider the following example:
This is a test sentence which cannot
be split
or it will not make sense
The only solution I can come up with is:
{
"en": {
"example": "This is a test sentence which cannot {linkOpen}be split{linkClose} or it will not make sense"
}
}
and then in the component template
<p v-html="$t('example', {
'linkOpen': `<a href="https://example/com" class="test-class test-another-class">`,
'linkClose: '</a>'
})
"></p>
Not exactly elegant however...
Edit: I've tested this and it doesn't actually work (can't put html in params) so now I'm really out of ideas!
You can come up with some simple markup for links and write a small transformation function, for example:
//In this example links are structured as follows [[url | text]]
var text = `This is a test sentence which
cannot [[https://example.com | be split]] or it will not make sense`
var linkExpr = /\[\[(.*?)\]\]/gi;
var linkValueExpr = /(\s+\|\s+)/;
var transformLinks = (string) => {
return text.replace(linkExpr, (expr, value) => {
var parts = value.split(linkValueExpr);
var link = `${parts[2]}`;
return link;
});
}
alert(transformLinks(text));
JSFiddle: https://jsfiddle.net/ru5smdy3/
With vue-i18n it will look like this (which of course you can simplify):
<p v-html="transformLinks($t('example'))"></p>
You can put the HTML into an element that is not part of the displayed DOM and then extract its textContent. This may not work for what you're actually trying to do, though. I can't tell.
new Vue({
el: '#app',
data: {
html: `This is a test sentence which cannot
be split
or it will not make sense`,
utilityEl: document.createElement('div')
},
methods: {
htmlToText: function (html) {
this.utilityEl.innerHTML = html;
return this.utilityEl.textContent;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<p v-html="html"></p>
<p>{{htmlToText(html)}}</p>
</div>
I have found myself in a similar situation, and I propose using Vue-i18n slots.
I have a JSON i18n file which had error messages that were html. These rendered fine but they will not be compiled as vue templates, and cannot have bindings. I want to call an onclick function when users click the link in a given error message.
In my example I have a cake-state json with some status messages:
// cake_state.json, where I want links in error messages to call a function when clicked
{
"state":{
"stage": {
"mixing": "Cake is being mixed. The current time is {time}",
"resting": "Cake is resting. The current time is {time}",
"oven": "Cake is cooking. The current time is {time}"
},
"error": {
"ovenIssue": "Oven of brand is malfunctioning. Click {email_support_link} to get help",
"chefIssue": "Chef is down. Click {email_support_link} to get help",
"leakIssue": "There is a leak"
},
}
}
Now if we have some Vue SFC, with the template as such:
<template>
<div>
<i18n :path="getMessage">
<!-- enter various i18n slots -->
<template #time>
<span>{{ getTime }}</span>
</template>
<template #email_support_link>
<!-- binding now works because it is not v-html -->
<a href="" #click.prevent="getRightSupportDepartment">here</span>
</template>
</i18n>
</div>
</template>
...
// js
computed: {
getTime(): string { //implementation ...},
getRightSupportDepartment(): string { //implementation ...},
//return strings that are keys to cake_state.json messages
getMessage(): string {
...
switch (this.cakeState) {
case Failure.Overheat:
return "state.error.ovenIssue";
case Failure.ChefIdle:
return "state.error.chefIssue";
case Failure.LeakSensor:
return "state.error.leakIssue";
So what we see here is:
the getMessage function provides us the key to the message in the i18n JSON. This is passed into i18n component
the <template #XXX> slots in the i18n component's scope are supplied with this key from the function, which gets the corresponding message, and then
if the relevant message has any of the keywords, it gets put in from the corresponding template.
To re-iterate, it helps to provide a means to have vue bindings to html elements which would otherwise be served from the i18n json as raw html.
For example now we might see "Oven of brand is malfunctioning. Click here to get help", and we can run an onclick function when user clicks 'here'.

Select2 AJAX doesn't update when changed programatically

I have a Select2 that fetches its data remotely, but I would also like to set its value programatically. When trying to change it programatically, it updates the value of the select, and Select2 notices the change, but it doesn't update its label.
https://jsfiddle.net/Glutnix/ut6xLnuq/
$('#set-email-manually').click(function(e) {
e.preventDefault();
// THIS DOESN'T WORK PROPERLY!?
$('#user-email-address') // Select2 select box
.empty()
.append('<option selected value="test#test.com">test#test.com</option>');
$('#user-email-address').trigger('change');
});
I've tried a lot of different things, but I can't get it going. I suspect it might be a bug, so have filed an issue on the project.
reading the docs I think maybe you are setting the options in the wrong way, you may use
data: {}
instead of
data, {}
and set the options included inside {} separated by "," like this:
{
option1: value1,
option2: value2
}
so I have changed this part of your code:
$('#user-email-address').select2('data', {
id: 'test#test.com',
label: 'test#test.com'
});
to:
$('#user-email-address').select2({'data': {
id: 'test#test.com',
label: 'test#test.com'
}
});
and the label is updating now.
updated fiddle
hope it helps.
Edit:
I correct myself, it seems like you can pass the data the way you were doing data,{}
the problem is with the data template..
reading the docs again it seems that the data template should be {id, text} while your ajax result is {id, email}, the set manual section does not work since it tries to return the email from an object of {id, text} with no email. so you either need to change your format selection function to return the text as well instead of email only or remap the ajax result.
I prefer remapping the ajax results and go the standard way since this will make your placeholder work as well which is not working at the moment because the placeholder template is {id,text} also it seems.
so I have changed this part of your code:
processResults: function(data, params) {
var payload = {
results: $.map(data, function(item) {
return { id: item.email, text: item.email };
})
};
return payload;
}
and removed these since they are not needed anymore:
templateResult: function(result) {
return result.email;
},
templateSelection: function(selection) {
return selection.email;
}
updated fiddle: updated fiddle
For me, without AJAX worked like this:
var select = $('.user-email-address');
var option = $('<option></option>').
attr('selected', true).
text(event.target.value).
val(event.target.id);
/* insert the option (which is already 'selected'!) into the select */
option.appendTo(select);
/* Let select2 do whatever it likes with this */
select.trigger('change');
Kevin-Brown on GitHub replied and said:
The issue is that your templating methods are not falling back to text if email is not specified. The data objects being passed in should have the text of the <option> tag in the text property.
It turns out the result parameter to these two methods have more data in them than just the AJAX response!
templateResult: function(result) {
console.log('templateResult', result);
return result.email || result.text;
},
templateSelection: function(selection) {
console.log('templateSelection', selection);
return selection.email || selection.id;
},
Here's the fully functional updated fiddle.

Alfresco: linking directly to workflow

I would like to start a workflow from the site links dashlet on my Alfresco site. Using Firebug to examine the POST gives me a URL that works, but it only displays the form without any UI:
http://localhost:8081/share/service/components/form?htmlid=template_x002e_start-workflow_x002e_start-workflow_x0023_default-startWorkflowForm-alf-id1&itemKind=workflow&itemId=activiti%24orpWorkflow&mode=create&submitType=json&showCaption=true&formUI=true&showCancelButton=true&destination=
Is this possible? And if so, how can I format the link to include the UI?
If not, are there custom dashlets out there designed for starting workflows?
When you select workflow from dropdown it will generate url based on selected workflow and redirect you to that.
Ex. For ParallelGroupReview workflow URL is.
http://localhost:8080/share/service/components/form?htmlid=template_x002e_start-workflow_x002e_start-workflow_x0023_default-startWorkflowForm-alf-id1&itemKind=workflow&itemId=activiti%24activitiParallelGroupReview&mode=create&submitType=json&showCaption=true&formUI=true&showCancelButton=true&destination=
Now if you use this url directly in browser you will be able to see same form but header and footer part will be missing, because those global components will not be avilable outside of share context.
If you see start-workflow.ftl you will be able to see header and footer components are inserted which are responsible for rest of the UI.
<#include "include/alfresco-template.ftl" />
<#templateHeader />
<#templateBody>
<#markup id="alf-hd">
<div id="alf-hd">
<#region scope="global" id="share-header" chromeless="true"/>
</div>
</#>
<#markup id="bd">
<div id="bd">
<div class="share-form">
<#region id="start-workflow" scope="template"/>
</div>
</div>
</#>
</#>
<#templateFooter>
<#markup id="al-ft">
<div id="alf-ft">
<#region id="footer" scope="global"/>
</div>
</#>
</#>
You can reuse same component just need to make sure header and footer are initialized properly.
I created an extension module which has the following target:
<targetPackageRoot>org.alfresco.components.workflow</targetPackageRoot>
I included the following piece in my extended start-workflow.get.html.ftl:
<#markup id="start-workflow-js" target="js" action="after">
<#script src="${url.context}/res/components/workflow/initiate-workflow.js" group="workflow"/>
</#>
to extend the default start-workflow.js of my own.
You'll need to change the following methods:
onReady: so it reads your param from the url to know which workflowdefinition to start and fire onWorkflowSelectChange
onWorkflowSelectChange: So it reads the workflowdefintion to load the form
You can make a little customization for Share.
For example, if you need to open the start form of any business process, you can find its index in the popup and add an additional parameter to the URL (let's say, openFormParam).
In start-workflow.js:
onReady: function StartWorkflow_onReady() {
// skipped ...
// get the additional parameter from the URL here
// var openFormParam = ...
if(openFormParam !== null) {
var p_aArgs = [];
var index = {index: 0}; // for the first workflow in the popup
p_aArgs.push(0, index);
this.onWorkflowSelectChange(null, p_aArgs);
}
return Alfresco.component.StartWorkflow.superclass.onReady.call(this);
},
// OOTB
onWorkflowSelectChange: function StartWorkflow_onWorkflowSelectChange(p_sType, p_aArgs) {
var i = p_aArgs[1].index;
if (i >= 0) {
// Update label of workflow menu button
var workflowDefinition = this.options.workflowDefinitions[i];
this.widgets.workflowDefinitionMenuButton.set("label", workflowDefinition.title + " " + Alfresco.constants.MENU_ARROW_SYMBOL);
this.widgets.workflowDefinitionMenuButton.set("title", workflowDefinition.description);
// Load the form for the specific workflow
Alfresco.util.Ajax.request({
url: Alfresco.constants.URL_SERVICECONTEXT + "components/form",
dataObj: {
htmlid: this.id + "-startWorkflowForm-" + Alfresco.util.generateDomId(),
itemKind: "workflow",
itemId: workflowDefinition.name,
mode: "create",
submitType: "json",
showCaption: true,
formUI: true,
showCancelButton: true,
destination: this.options.destination
},
successCallback: {
fn: this.onWorkflowFormLoaded,
scope: this
},
failureMessage: this.msg("message.failure"),
scope: this,
execScripts: true
});
}
},

ASP.Net MVC validation not working with Bootstrap Select Picker

I've issue with ASP.NET MVC validation not working with Bootstrap Select Picker, If I remove the selectpicker class from dropdown list the validation working fine and if I added it the validation not working , Any help please
The MVC Code :
#Html.DropDownListFor(m => m.SelectedLocationID, Model.lstOfLocations, "Select Delivery Location", new { #class = " selectpicker", #placeholder = "" })
#Html.ValidationMessageFor(m => m.SelectedLocationID)
The Jquery Code With Valida-min.js
$(".SearchForm").validate({
ignore: ':not(select:hidden, input:visible, textarea:visible)',
rules: {
SelectedLocationID: {
required: true
}
},
errorPlacement: function (error, element) {
if ($(element).is('Select Delivery Location')) {
element.next().after(error);
} else {
error.insertAfter(element);
}
}
})
Thanks
I stumbled upon this question while searching for fix for same issue.
The problem arises from fact, that Bootstrap hides original select element and creates it's own elements to handle UI for dropdown list. In the meantime, jQuery validation by default ignores invisible fields.
I fixed this with workaround which combines changing validation ignore list and finding parent form. Final code snippet in my case looks like this
if ($(".selectpicker")[0]) {
$(".selectpicker").selectpicker();
$('.selectpicker').parents('form:first').validate().settings.ignore = ':not(select:hidden, input:visible, textarea:visible)';
}
There still could be potential issues, but for my needs this solution works good enough.
The problem is caused by the display:none property given from bootstrap select skin to the original select (jQuery validate ignores hidden fields).
It could be avoided working only with CSS keeping back visible the original select but giving it some properties to avoid visibility
select.bs-select-hidden, select.selectpicker
{
display:block !important; opacity:0; height:1px; width:1px;
}
I guess, editing of bootstrap-select.js may solve this issue permanently.
I have tried something like this:
$(document)
.data('keycount', 0)
.on('keydown.bs.select', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', Selectpicker.prototype.keydown)
.on('focusin.modal', '.bootstrap-select [data-toggle=dropdown], .bootstrap-select [role="listbox"], .bs-searchbox input', function (e) {
//Validation handler: Kartik
var $thisParent = $(this).parent(".bootstrap-select");
if ($thisParent.find("ul").find("li:first").hasClass("selected")) {
if ($thisParent.next("span").length > 0)
$thisParent.next("span").css("display", "block");
}
else {
if ($thisParent.next("span").length > 0)
$thisParent.next("span").css("display", "none");
}
e.stopPropagation();
});
Its working fine to me.

datetimepicker and Backbone.View Interaction

This is the code:
var MyModel = Backbone.Model.extend({
defaults: {std:"",
pod:""
}
});
var MyView = Backbone.View.extend({
tagName:'ul',
events: {
'change input' : 'changed', // When input changes, call changed.
'hover .std' : 'timepick', //
'mouseout .std': 'doesntwork'
},
template:_.template($('#mytemplate').html()),
initialize: function() {
this.model.bind('change:pod',this.render,this); // When pod changes, re-render
},
timepick: function(e) {
$('.std').each(function(){
$.datepicker.setDefaults({dateFormat:'mm-dd'});
$(this).datetimepicker({timeFormat:'hh:mm',ampm:false});
});
},
doesntwork: function() {
// Would this.model.set here but mouseout happens when you select date/time values with mouse
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
changed: function(e) {
var value = $(e.currentTarget).val(); // Get Change value
var cls = $(e.currentTarget).attr('class'); //Get Class of changed input
var obj = {};
obj[cls] = value; // The model variables match class names
this.model.set(obj); // this.model.set({'std':value})
}
});
I have a datetimepicker in the UI I'm working on, and having difficulties assigning the value that is selected from the datetimepicker to MyModel.
It appears from using console.log output that 'change input' is triggered when clicking on the DTP and assigns the default value of (MM-DD 00:00). Even when you select a different date/time value than the default, the 'change input' is not triggered again, unless you click on the input box (for a second time), and then the correct value is assigned. Not very user-friendly.
So I had the idea that I would just assign the value on mouseout, which didn't work since mouseout happens when you start to select date/time values. I also tried blur, and that didn't work either.
Where am I going wrong?
Edit: Here is a jsfiddle.net link that illustrates my problem http://jsfiddle.net/9gSUe/1/
Looks like you're getting tripped up by jQuery UI's CSS. When you bind a datepicker to an <input>, jQuery UI will add a hasDatepicker class to the <input>. Then you do this:
var cls = $(e.currentTarget).attr('class');
on the <input> and get 'std hasDatepicker' in cls.
There are too many things that will mess around with class so you're better off using something else to identify the property you want to change. You could use an id if there is only one std:
<!-- HTML -->
<input id="std" class="std" ...>
// JavaScript
var cls = e.currentTarget.id;
or the name attribute:
<!-- HTML -->
<input name="std" class="std" ...>
// JavaScript
var cls = $(e.currentTarget).attr('name');
or perhaps even a HTML5 data attribute:
<!-- HTML -->
<input class="std" data-attr="std" ...>
// JavaScript
var cls = $(e.currentTarget).data('attr');
I think the name attribute would be the most natural: http://jsfiddle.net/ambiguous/RRKVJ/
And a couple side issues:
Your fiddle was including multiple versions of jQuery and that's generally a bad idea.
You don't have to build an object for set, you can say just m.set(attr, value) if you're just setting one attribute.
You don't have to $(this.el) in your views, newer Backbones have this.$el already cached.
console.log can handle multiple arguments so you can say console.log('Std: ', attrs.std, ' Pod: ', attrs.pod, ' Poa: ', attrs.poa); instead of console.log('Std: ' + attrs.std + ' Pod: ' + attrs.pod + ' Poa: ' + attrs.poa); if you don't want + stringify things behind your back.

Resources