JQueryMobile select element not working within AngularJs ng-switch directive - angularjs

I have this HTML:
<div ng-switch on="MyViewType">
<div ng-switch-when="B">
<select id="selectCatagoryFood2" data-role="listview" data-native-menu="true" ng-options="foodCatagory as foodCatagory.Description for foodCatagory in foodCatagories" ng-model="foodCatagory" ng-change="changeFoodCatagory(foodCatagory)">
</select>
</div>
</div>
The select appears empty and do nothing if I select another value.
Example (the first "select" works correctly, but the second doesn’t show selected value): http://plnkr.co/edit/yrXa70?p=preview
The problem is similar to this other post, but this solution doesn’t work (the “Refresh” launches an exception).
Select Value not Init in AngularJs and JQueryMobile
I think the problem is that this directive (ng-switch) removes and adds elements from the DOM and JQuery Mobile loses some objects created in the initializations of the elements.

Code from your example:
var unbindWatcher = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue && oldValue === undefined) {
element.selectmenu('refresh');
unbindWatcher();
}
});
The element.selectmenu('refresh'); will be called when ngModel (which is the selected option) gets a new value and the old value is undefined.
This will work for the first select because it will:
First get rendered and initialized as a select menu by jQuery Mobile
Later get new data and be refreshed
However, in the second case, the HTML inside <div ng-switch-when="B"> will not get rendered until MyViewType is actually B, which is at the same time the data for the select is ready.
This means two things:
When the code in the $watch gets executed for this element, the ngModel will already be available, both newValue and oldValue will be Object {Description: "1"}, and the if statement will not be executed.
If you tried to call element.selectmenu('refresh'); it would throw
an error, as the element has not yet been initialized as a
select menu by jQuery Mobile.
If you want to populate the select menu with data from the $scope and the data is available at time of initialization, I would:
Tell jQuery Mobile not to automatically turn the select into a select menu by setting data-role="none"
Turn in into a select menu manually by calling element.selectmenu(); from the directive
Otherwise you would need a good way to know when the element has been rendered and initialized by jQuery Mobile, then call refresh.
Modified directive for both cases:
var unbindInitializationWatch = scope.$watch(attrs.ngModel, function(newValue, oldValue) {
if (newValue && oldValue === undefined) {
element.selectmenu('refresh');
unbindInitializationWatch();
} else if (newValue && oldValue) {
element.selectmenu();
unbindInitializationWatch();
}
});
Working example: http://plnkr.co/edit/9jR7ko3j8ugUr1suB8ws?p=preview
Note that ngSwitch creates a new scope, and in order for the both select menus to share selected option I have in the example moved the model for the selected option into an object (More on this here if not familiar with how it works).
Also added an additional $watch to sync the both select menus.

Related

Angular scope variable update not reflected in UI

We are working on an HTML page which makes use of a Bootstrap tooltip on a certain <span> tag. For those who have not heard of tooltip, it is a popup of sorts which appears when hovering over the element to which it is attached. Here is a screenshot showing the <span> in question, and what happens on hover:
The premise behind adding the tooltip was that in the event that we truncate the text, the tooltip would provide an option for viewing the entire text.
However, we would now like to condtionally show the tooltip only when there is no ellipsis in the text. We defined the tooltip-enable property in the <span>:
<span uib-tooltip="{{someName}}" tooltip-placement="right" tooltip-enable="{{showToolTip}}">{{someNameShortened}}</span>
The key thing here is tooltip-enable="{{showToolTip}}", which binds the property to a scoped variable in the controller for this page. And here is the relevant (and abbreviated) controller code:
mainApp.controller('repoListController',['$scope', '$rootScope', ...,
function($scope,$rootScope, ...) {
$scope.showToolTip = false;
var repositoryList= function(){
repositoryService.getRepositoryList(function(data) {
var repoList = data;
repoList.shortenedDisplayName = repositoryService.getShortRepoName(repoList.repoName, DISPLAY_NAME_MAX_LENGTH);
// if the repository's name be sufficiently large (i.e. it has an ellipsis)
// then show the tooltip. Otherwise, the default value is false (see above)
if (repoList.repoName.length > DISPLAY_NAME_MAX_LENGTH) {
$scope.showTooltip = true;
}
});
}
repositoryList();
}]);
Based on the research I have done, the common solution for why a change to a scoped variable is not reflected in the UI is to run $scope.$apply(), or some variation on this. Running apply(), as I understand it, will tell Angular JS to do a digest cycle, which will propagate changes in the scope to the UI. However, trying to do an apply() from the code which toggles showToolTip resulted in errory. I inspected the value of $scope.$root.$$phase while running the code which updates the showToolTip variable, and the phase was digest.
So now I am at a loss to explain this. If the code is already in a digest, then why would changes not be reflected in the UI? Also, if the code is already in digest, then how could I force Angular to sync the changes to the UI?
Two things need fixing...
Don't use string interpolation for your boolean showToolTip
<span uib-tooltip="{{someName}}" tooltip-placement="right"
tooltip-enable="showToolTip">{{someNameShortened}}</span>
JavaScript variables / properties are case sensitive. In your getRepositoryList handler, you have $scope.showTooltip. It should be $scope.showToolTip (two capital "T"s)
Crappy Plunker demo ~ http://plnkr.co/edit/W7tgJmeQAJj0fmfT72PR?p=preview

Angularjs form.$dirty

I'm able to find form data is changed or not using $dirty.
ex: I changed text box or drop down and then $dirty become true. If I reverted to old data still it is true. I need to know if my changes are reverted or not. Do we have any property in Angularjs? If property is true I want to enable save button otherwise it should be disable.
https://docs.angularjs.org/api/ng/type/form.FormController
I need to implement around 10 pages and each page has 10 text boxes and a couple of drop downs. So I don't want track each control manually in my pages.
You can try using this module: https://github.com/betsol/angular-input-modified
From the README file:
This Angular.js module adds additional properties and methods to the
ngModel and ngForm controllers, as well as CSS classes to the
underlying form elements to provide end-user with facilities to detect
and indicate changes in form data.
This extra functionality allows you to provide better usability with
forms. For example, you can add decorations to the form elements that
are actually changed. That way, user will see what values has changed
since last edit.
Also, you can reset an entire form or just a single field to it's
initial state (cancel all user edits) with just a single call to the
reset() method or lock new values (preserve new state) just by calling
overloaded $setPristine() method.
DISCLAIMER: I haven't tried it myself and I notice the author overwrites the ngModel directive instead of adding a decorator, which could be dangerous...but at the very least, you can look at the source and get an idea of how to write your own service or directive with similar functionality.
Even though it does not follow the usage of $dirty, but an implementation similar to this might be helpful for you in the case of a Save button on update.
Inside your html:
<form name="testForm" ng-controller="ExampleController" ng-submit=" save()">
<input ng-model="val" ng-change="change()"/>
<button ng-disabled="disableSave">Save</button>
</form>
Inside your controller:
.controller('ExampleController', ['$scope', function($scope) {
$scope.disableSave = true; // Keep save button disabled initially
$scope.val = 'Initial'; // Initial value of the variable
var copyVal = $scope.val; // Copy Initial value into a temp variable
$scope.change = function() {
$scope.disableSave = $scope.val === copyVal;
};
$scope.save = function() {
// Save the updated value (inside $scope.val)
console.log($scope.val);
// Re-disable the input box (on successful updation)
copyVal = $scope.val;
$scope.disableSave = true;
};
}]);
Here is a working plunkr for the same.

How to design angular app with multiple models and select fields

What I'm trying to build is a SPA containing multiple selects on one page, where each select has it's own model. Choosing different options in the select boxes may show/hide other fields on the page and modify data for other selects (load different sets). Finally options in two selects may change the url (so when page is loaded with a proper address, those two options will be preselected).
Now I'm wondering what'll be the best approach here.
First. Is it worth to switch to ui-router in this case ?
Second. I need to write a custom directive for this select, that will have the following functionality
load data collection
display data and remember selection
reload it's data collection when other select triggered it
reload data for other selects
Now I've written directives before but never anything this (I think) complex. That's why few questions come to my mind.
How can I bind different data to my directive ?
Should this be just a single massive complex directive or divide it to smaller parts (like one directive for showing closed select box and another one to show the list and so on) ?
How can I trigger event when data should be changed and listen to a similar event from another select. Via controller ?
Thanks in advance for all your help.
What I can suggest is keep it MVC. Here is one of the solution -
Create a controller to store model data (here $scope.selectOptions inside controller)
Pass this values to 'select directive' instance to display
Whenever user select the value in directive, pass that selected value to controller (lets say in controller $scope.selectedValue is holding that value)
In controller add $watch on $scope.selectedValue and in its callback call for different data set for another select option. (lets say storing that data set in $scope.anotherSelectOption )
Pass this $scope.anotherSelectOption to '2nd directive' instance
Whenever user select the value in 2nd directive, pass that selected value to controller (lets say in controller $scope.anotherSelectedValue is holding that value)
In controller add $watch on $scope.anotherSelectedValue and in its callback change url thing you want to do
Here's your HTML will look like -
<div ng-controller="MyCtrl">
<select-directive selection-option="selectOptions", selected-option="selectedValue"> </select-directive>
<select-directive selection-option="anotherSelectOptions", selected-option="anotherSelectedValue"> </select-directive>
</div>
Here's your controller, it will look something like this -
yourApp.controller('MyCtrl', function($scope) {
$scope.selectOptions = []; // add init objects for select
$scope.selectedValue = null;
$scope.$watch('selectedValue', function() {
// here load new values for $scope.anotherSelectOptions
$scope.anotherSelectOptions = [] // add init objects for select
$scope.anotherSelectedValue = null;
});
$scope.$watch('anotherSelectedValue', function() {
// here add code for change URL
});
});
Here's your directive scope is
yourApp.directive('selectDirective', function() {
return {
templateUrl: 'views/directives/select.html',
restrict: 'E',
scope: {
selectionOption: '=',
selectedOption: '='
}
};
});

ng-show binding to string expression in angular

Is there a way to bind a ng-show expression to a string which contains the bind expression itself?
For Instance:
field={};
field.a=true;
field.showExpression='a == true';
<input ng-show="field.showExpression">
I´ve tried <input ng-show="{{field.showExpression}}"> as well, but none of them seems to work.
I want the bind to stay active, so that when the field.a object changes from true to false the expression gets evaluated again, hiding the input.
Just as background, i´m trying to implement dependant dropdowns, so my showExpressions should be of form field.showExpression='maindropdownValue!=null', and whenever the maindropdown which will be bound to the maindropdownValue gets selected the second one gets displayed.
I´m using angular 1.0.8
showExpression is evalued as a String not as a JS code. You have to use a function instead.
$scope.isTrue = function() {
return $scope.field.a; // or a more complex check
}
//
ng-show="isTrue()"
if you only have to check for a boolean, you can check var directly in the view:
ng-show="field.a"
If you really want to use eval, this is what you want:
ng-show="$parent.$eval(field.showExpression)"
link: http://docs.angularjs.org/guide/expression

angularJS directive not immediately honoring ng-show/ng-hide

I have the following section of HTML in an angularJS application. The <div/> tag for appointment-list is showing a listing of appointments. This directive is basically just a table.
<div ng-show="loading">Loading...</div>
<div ng-show="!loading && (appointments.length == 0)">No Appointments Found</div>
<div ng-hide="loading || (appointments.length == 0)">Test123</div>
<div ng-hide="loading || (appointments.length == 0)" appointment-list source="appointments" appointment-selected="appointmentSelected(appointment)"></div>
I then have the following in my controller. I am setting a loading variable while things are in-flight, and then I also filter the appointments on the page according to text in a text box.
$scope.$watch('selectedDate', function(newVal, oldVal) {
if (newVal) {
$scope.loading = true;
Appointment.query({year: newVal.getYear()+1900, month: newVal.getMonth()+1, day: newVal.getDate()}, function(data) {
$scope.allAppointments = data;
$scope.appointments = $scope.filterAppointments();
$scope.loading = false;
});
}
});
My issue is that the hiding of the div for my custom directive isn't happening properly. The table should be disappearing exactly along with the "Test123" string and its not. When I go from a selected date with the table populated to a date with nothing on there, the "Test123" will be replace with the loading (therefor its being hidden, and loading being shown) but the table remains until after the loading process is complete at which point the table will disappear
Can someone explain why the delay? Why is the directive not responding exactly like the div above it?
Edit
Here is a plnkr which shows the issue: http://plnkr.co/edit/khxQuaM6sxTx5RszvowX?p=preview
Basically click on the buttons at the top to load the two datasets. I have a timeout in there to simulate some of the think time on the server. Whenever you see "Loading..." the div for the appointmentList table should not be shown since ng-hide will evaluate to true because loading is true, yet is doesn't disappear.
You need to use $parent to access the model loading since the directive appointmentList creates an isolated scope. Make the following change to the last div containing the table and you will achieve the effect you want.
<div ng-hide="$parent.loading || (appointments.length == 0)" appointment-list source="appointments" ... ></div>
You don't need to use $parent to refer to appointments, since you pass this model to the directive. But there is no harm to add $parent like $parent.appointments.length == 0, since you have appointments defined anyway in the parent scope.
Btw, you should also set appointments to be empty in the watcher like this
if (newVal) {
$scope.loading = true;
$scope.appointments = []; //add this
to make the condition appointments.length == 0 useful.
Is $scope.appointments being set within the same tick as $scope.loading?
If $scope.filterAppointments is doing something asynchronous, you want to make sure $scope.loading is set to false at the end of that process.
In my case, angular didn't want to show/hide when applied to an ul > li element, in IE or Chrome. Changing it to a div works perfectly. I am on angular 1.2.14. Not sure if this is a bug or not, but it seems to be.
I had the same problem. I used ng-cloak, it keeps the browser from displaying the template while my application is loading.
Take a look at this:
http://weblog.west-wind.com/posts/2014/Jun/02/AngularJs-ngcloak-Problems-on-large-Pages

Resources