I'm currently altering a website which was devided into iframes to now being embedded (with AngularJS), without any iframes.
There is a big problem with this: I had a Kendo UI auto-complete drop-down element for selecting locations. The behavior with iframe and embedded is totally different concerning scrolling in the area around/beneath the auto-complete drop-down.
Old app: the site (iframe) around scrolled and the drop-down still was visible and moved with the rest of the site until you selected an item.
New app: the drop-down box closes immediately and you have to retype some input to get it open again. Unacceptable usability!
How do I get an auto-complete drop-down (doesn't have to be Kendo if not possible) which does have the OLD scrolling behavior in embedded mode?
Well, I found a workaround which works fine for me:
In the directive html, I added a callback for the event k-close. In this callback in the controller I prevented the default behavior of close event (of course under specific conditions) with the following code in the controller:
$scope.closeCallback= function (e) {
if (someConditionForWhichDropdownShouldntBeClosed) {
e.preventDefault();
}
};
and here's the HTML of the directive:
<input
ng-model="model"
kendo-auto-complete="source"
k-data-source="locationDataSource"
k-select="selectLocation"
k-close="closeCallback">
In my case, I prevented the Dropdown being closed as long as no item was selected.
For this I added a new boolean scope variable which was false by default, was set true if dropdown opened:
$scope.locationDataSource = new kendo.data.DataSource({
type: "json",
serverFiltering: true,
transport: {
read: function (options) {
$scope.keepKendoDropdownOpen = true;
someOtherFuncionalityAfterSelectingAnItem();
}
}
});
and set false again after selecting (in the callback of the directive's k-select).
Would be nice to also watch if the user presses ESC or something, but until now it's okay enough.
Please feel free to make my solution better or post other solutions! :-)
Related
This is an angular 1 app.
I have a directive that looks like this:
function addLiquidity() {
return {
restrict: 'E',
scope: {
expanded: '='
},
templateUrl: 'liquidity.html',
controller: 'addLiquidityCtrl'
};
}
And the html that looks like this:
<add-excess-liquidity expanded="addExpanded"></add-excess-liquidity>
In another html I have this:
expanded: {expanded}
This is false by default thanks to the controller that in its relevant part contains this:
$scope.addExpanded = false;
Now, when clicking a button to show form for adding new stuff, "addExpanded" is set to true.
When then the button to actually save the form is clicked "addExpanded" is set to false again:
$scope.addExpanded = false;
I can see in the controller that that function is called and that addExpanded behaves correctly in the controller.
Actually, it behaves correctly in the view too. So the value in html:
expanded: {expanded}
reflects the value in the controller.
However, as soon as I switch to another tab (this app is using nav-tabs) and come back to the tab I was before, then the value in html is not reflecting the controller anymore(which is still doing its job like before, going through the same steps as before, included setting the "expanded" to false).
I tried forcing view update with both timeout and "apply" but didn't help. Also, it seems somwehere else an apply is fired at the end of every function.
I really don't understand what's wrong here. Where should I look? Something happen (I guess) when leaving the tab the first time. But so many things happen so it is not easy to debug. Any clue anybody?
EDIT
I added a watch like this:
$scope.$watch( 'addExpanded',
function(newValue, oldValue){
console.log('addExpanded Changed');
console.log(newValue);
console.log(oldValue);
}
);
First time I go to the tab then it will print to console. I then leave the tab, then come back to it. And this time, when clicking on button, nothing is printed to the console.
Roughly, what happens when I change tab is that the div containing the tab-content is set to "display:none/block" (I guess) as it is pure css that is showing tab content upon selection.
In the main controller a few things are done depending on which tab was selected, but I cannot see things that would "destroy" the connection between the GUI and the controller/directive.
Also, each time I go to that tab, I see that the javascript connected to the directive is initiating. And the variable "expanded" is reset to false.
The issue is that, for some reason, the connection between javascript and the GUI is in some way "lost" when visiting the tab the second time (and for all the future until I refresh the page).
I have a page built in angular. The page has a set of data displayed that's paginated. I have the page searchable with a search box. The pagination is build by hand due to some custom rules about what comprises each page. But building the pagination is set in a buildPages() method, and from there it's a simple call to build the pages that directly updates the scope the page draws from.
So typically a search works like so:
Type in search.
Watcher catches the change on search.
Watcher sets search var.
Watcher calls buildPages();
buildPages rebuilds pagination and updates scope.
Scope is update, pagination and displayed data is redrawn.
Now all of this works just fine. But I needed to add in a dialog to ask the user if they want to change the search or leave it, and this dialog only appears sometimes based on certain criteria.
My problem is that if I open a dialog and call buildPages() from a button click in the dialog, it properly updates the scope, but it doesn't redraw the page.
So, for example, you have 50 pages and you're displaying page 1 of 50. You then type in a search. The search filters out 25 of those 50 pages, leaving you with 25 pages left. The page you were initially on is also filtered out. Without the dialog, typing in the search will result in you appearing on page 1/25 on the first filtered page. However, triggering the search from the dialog will still show you on page 1 of the unfiltered page, at page 1/50. Clicking next on the pagination control will properly take you to the second filtered page 2/25, and clicking back from there will properly show you on page 1/25 of the filtered pages.
Even doing something as simple as triggering a hover will cause the page to properly redraw correctly.
What's going on with this? Why is the dialog preventing the redraw?
Here's the code I'm using in my watcher (slightly simplified).
$scope.$watch('searchText', function(newVal, oldVal) {
currentSearch = newVal;
if(newVal !== oldVal){
if(displayFlag){
j$( "#dialog-confirm").dialog({
resizable: false,
width:450,
height:240,
modal: true,
buttons: {
"Search": function() {
j$( this ).dialog( "close" );
buildPages();
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}});
}
else
{
buildPages();
}
}
});
I've tried including a return of either true or false in the search click. I've also tried switching the order of the dialog close and buildPages in the search click.
I am not sure if j$() is an angular library. It appears that it may not be.
Remember that Angular does not know about any type of events or changes except its own, therefore, if you are performing an action with jQuery or another library like that .dialog, after you performe the call, you may need to call $scope.$digest(); or $scope.$apply() to trigger the angular digest cycle.
scope.$digest() will only fire watchers on current scope, scope.$apply (recommended) will evaluate passed function and run $rootScope.$digest().
buttons: {
"Search":
function() {
j$( this ).dialog( "close" );
buildPages();
$scope.$apply(); //After function is executed a cycle will occur.
},
Cancel: function() {
j$( this ).dialog( "close" );
return false;
}
}
I believe in your situation that the view may not being updated because angular isn't being told to perform a digest cycle.
I'm using angular and ui-router with the extras so that I can use sticky state for maintaining listview scroll position on returning from the detail page in my mobile spa app.
My list is a kendo mobile listview. I'm implementing crud and would like to reflect the changes made in the listview when returning. I do not use the $state.go in my back buttons, but use $window.history.back because I want the back buttons to work "as expected", so I cannot use the reload parameter of that function.
I wired up the onReactivate and inject the service that has the flag to tell it whether crud operations have been done and to reload the list. This all works and I can successfully determine if I need to reload the screen.
So, I try to use the kendo.mobile.ui.ListView refresh method. I use angular.element("#idoflistview").data("kendoMobileListView").refresh(). It does find it and does refresh it, but all of the items are set to have a style="transform: translate3d(0px, 0px, 0px);", so they are all overlapping each other.
I originally did try to just do a $state.reload() and rebuild the page, and it did work in that it calls my init function in the controller and I could use the flag to know to request the data right way, but the listview would not build. The request for data was sent and retrieved, but the listview was empty.
I would prefer to just refresh the list if possible because the service has the kendo datasource with the updated data already, so there really is no need to make a call to the database again.
So, my setup for the listview works fine normally, but only breaks if I refresh in the onReactivate in that they are all there, but overlap each other because every li in the list starts at 0 (the update is in there though, so it did refresh).
I'm assuming that it is due to the fact that it needs to do it later. If so, how can I do that?
Any ideas on how to get the refresh method to work properly? Or is there a better way to do this?
Here is the abstract view:
<div ui-view="activityinquirylist" ng-show="$state.includes('activityinquiry.list')"></div>
<div ui-view="activityinquirydetails" ng-show="$state.includes('activityinquiry.details')"></div>
<div ui-view="activityinquirycrud" ng-show="$state.includes('activityinquiry.crud')"></div>
Here is how I'm declaring my listview:
<div id="activityListScroller" kendo-mobile-scroller="activityListScroller" k-pull-to-refresh="true" k-pull="vm.pullToRefresh" k-pull-offset="200" class="scroller-header-footer">
<ul id="activityListView" kendo-mobile-list-view="activityListView" k-data-source="vm.activityInquiryService.activityInqiuryDataSource" k-template="vm.activityTemplate" k-on-click="vm.listClicked(kendoEvent)" k-auto-bind="false" k-load-more="true"></ul>
</div>
Here is the code for the state:
.state('activityinquiry', {
//sticky: true,
abstract: true,
url: '/activityinquiry',
templateUrl: 'app/views/cm/activityinquiry.html'
})
.state('activityinquiry.list', {
url: '/activityinquirylist',
views: {
'activityinquirylist#activityinquiry': angularAMD.route({
templateUrl: 'app/views/cm/activityinquirylist.html',
controller: 'activityinquirylistController',
controllerUrl: 'controllers/cm/activityinquirylistController',
controllerAs: "vm"
})
},
sticky: true,
deepStateRedirect: false,
onReactivate: function (activityInquiryService) {
console.log("reactivating");
if (activityInquiryService.model.reloadList && activityInquiryService.model.reloadList == true) {
activityInquiryService.model.reloadList = false;
var item = angular.element("#activityListView").data("kendoMobileListView");
if (item) {
item.refresh();
}
}
}
})
EDIT:
After further investigation, I have found that it is due to the kendo.mobile.scroller. It is not available at the time that I am refreshing the listview, so it is somehow messing up how the listview does it's drawing. This makes no sense to me because the listview is inside the scroller (and still is if I inspect after).
If I remove the scroller, it works as expected, but I need that scroller because I am not using kendo views and have a footer on the page, so it will scroll with the content and I am using the pull to refresh functionality of the scroller (don't want to use the listviews pull to refresh because it does not allow me to handle how the refresh happens, while the scroller does).
Another Edit:
I have tracked it down to the fact that it only happens when I have loadMore true on the listview. If I turn this off, then it works fine.
So, in the code example above, if I set k-load-more="false" it works. I tried it with endless-scroll too and both have the same effect.
I need to display the form in read-only mode , I am creating the form based on Json using angular-formly . Checked the link http://angular-formly.com/#/example/other/read-only-form which works for text input , please suggest how to set read-only for radio , multicheckbox and checkbox
we do it via jQuery:
$(document).ready(function () {
$('input').click(function (e) {
e.preventDefault();
});
});
OR if you want to "pretend" you are using "angular" to do this, even though you are actually using jQuery, and want to type out a whole lot more code to write the exact same thing(literally, it will simply run the above code in jQuery as angular.element is an "alias" for jQuery---straight out of the angular docs: https://docs.angularjs.org/api/ng/function/angular.element), you can do:
angular.element(function() {
angular.element('input').trigger('click')(function(e) {
e.preventDefault();
};
});
This way you can look cool and spout off nonsense like "It's bad practice to run angular and jQuery together" because you have no clue what you are talking about and just read that somewhere.
This works for all input fields. If you have buttons or datepickers with buttons, etc the easiest way to do it is to set a readOnly property on the model in the form and then set it to true on page where you want it to be read only then set the ng-show on the buttons to: ng-show="!model.readOnly"
(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});