Use inputs in AngularJS Material bottom-sheet - javascript

The problem
I'm designing a Cordova hybrid app using the (very) new AngularJS Material Design dependency.
I have a log in form in a bottom sheet called via $mdBottomSheet service. Example below:
$scope.showLogin = function ($event) {
$mdBottomSheet.show({
templateUrl: 'views/login/login.html',
controller: 'loginCtrl'
})
};
The contents of views/login/login.html are:
<md-bottom-sheet ng-controller="loginCtrl" layout="column">
<form name="signInForm" ng-submit="submitPassword()">
<md-list>
<md-item>
<md-progress-circular md-mode="indeterminate" ng-show="loading"></md-progress-circular>
</md-item>
<md-item>
<md-text-float label="Email address" ng-model="username" required>
</md-text-float>
</md-item>
<md-item>
<md-text-float type="password" label="Password" ng-model="password" required>
</md-text-float>
</md-item>
<md-item>
<md-button class="md-primary md-raised submit" type="submit">Sign in</md-button>
</md-item>
</md-list>
</form>
</md-bottom-sheet>
Everything runs and displays just fine.
HOWEVER! when I go to click on the input, the focus is never given to the input, and instead the md-bottom-sheet element is dragged back and forth. Clicking on the button (when not disabled) fires just fine, but the click on the input to give it focus is never recognized.
Things I've tried
Adding ng-click="return false", 'ng-click="$event.preventDefault()"`
Adding a -webkit-transform: translate3d(0, 80px, 0) !important; rule to the CSS, as that's the default state of the property which is being altered on drag.
Adding angular.element('md-bottom-sheet').on('click', function() { return false; }); to an ng-init block.
Doing the same as above (using jQuery to try to ham-hand this) on the dragstart event.
The question
How can I use an input in a bottom sheet with material design, given I've tried every workaround that I know to get this to work for touch?
Caveats
1. I would love to offer a live example for this, but I can't find a CDN source for angular/material
2. This only happens on a mobile device, no idea if it will happen on a mobile website as I've only tested it in a Cordova hybrid app
3. There are no examples of this I've found via search, so I can't even point you to a resource that might emulate the problem.
4. Basically, this is going to be very hard to reproduce.
Update
One possible fix I've identified:
The following block in the function BottomSheet(element) "class" for the provider has the following:
function onTouchStart(e) {
e.preventDefault();
/* do the rest of the code */
}
Changing that e.preventDefault() to the following does allow normal input behavior, but requires that I fork their repo.
if (e.target.tagName.toLowerCase() !== 'input')
e.preventDefault()
Is there a solution that doesn't require me to fork this for such a minor change?

You were near the solution. You had to use stopPropagation() instead of preventDefault(). Here is the simple approach:
Directive (.coffee)
angular.module('Expedite').directive 'stopEvent', ->
restrict: 'A'
link: (scope, element, attr) ->
element.bind attr.stopEvent, (e) ->
e.stopPropagation()
View (.slim)
input ng-model="user.email" stop-event='touchstart'
This way you will not have to fork material repo.

So I'm running into exactly the same issue. The code above works for pre 0.6.1 - though doesn't work so well afterwards.
I have however found that moving that e.preventDefault() (minus the if() logic) down to the onTouchMove(e) function, seems to fix all scroll glitches (atleast that I can see) - and still allows inputs to function normally.
Agree, it would be super if the ng-material guys could incorporate something like this into the repo to make $mdBottomSheet implementation more flexible.

Related

dojo ValuePickerDatePicker on Android device

The Dojo mobile DatePicker - dojox.mobile.ValuePickerDatePicker - is behaving incorrectly:
Clicking on the plus(+) and minus(-) buttons for the year updates the day value and the otherway around.
This only appears on the device and never in a browser during development. Have reproduced on multiple Android devices.
This is also somewhat intermittent in that the steps to reproduce are not exactly the same every time. However once it starts to go wrong it continues to be wrong...
To reproduce: in the Date Picker widget, repeatedly and randomly click on the plus(+) and minus(-) buttons and on the editable fields. Eventually the fields will start to update incorrectly. (I would like it to be more predictable)
Have never reproduced the error on my PC/MAC.
I have a suspicion that the predictive text on the device is interfering, but I have no proof of that.
You can reproduce the error with the widget/Date Picker on its own. Tested with Dojo version 1.9.4 and 1.10.1. The Date Picker is created declaratively:
<div id="dateSelectorDatePicker" data-dojo-type="dojox.mobile.ValuePickerDatePicker" data-dojo-props="slotOrder:[2,1,0]"></div>
Try this example in your browser on a Android device http://jsfiddle.net/sport_johan/q943mbrs/1/
We fixed the problem by creating a new versions of the dojox.mobile.ValuePickerSlot
class and the dojox.mobile.ValuePickerDatePicker class.
In the MyValuePickerSlot which is a copy of ValuePickerSlot we are using click events instead touch events.
So first copy ValuePickerSlot.js and then inside MyValuePickerSlot.js you remove the following handlers:
// this.connect(this.plusBtnNode, touch.press, "_onTouchStart"),
// this.connect(this.minusBtnNode, touch.press, "_onTouchStart"),
and instead add
this.connect(this.plusBtnNode, "onclick", "_onClick"),
this.connect(this.minusBtnNode, "onclick", "_onClick"),
You can also remove the functions _onTouchStart, _onTouchMove and _onTouchEnd since you wont be using them anymore.
You now have to create a new class MyValuePickerDatePicker which inherits all functionality from the original class but you override the constructor that creates the slot classes which now are the new MyValuePickerSlot.
define(["dojo/_base/declare",
"dojo/_base/lang",
"dojox/mobile/ValuePickerDatePicker",
"myApp/dojox-fix/MyValuePickerSlot"],
function (declare, lang, ValuePickerDatePicker, MyValuePickerSlot) {
return declare("myApp.dojox-fix.MyValuePickerDatePicker",[ValuePickerDatePicker], {
// Override of the ValuePickerDatePicker to fix a touch event bug
// on Android device
constructor: function (args) {
this.slotClasses = [
MyValuePickerSlot,
MyValuePickerSlot,
MyValuePickerSlot
];
lang.mixin(this, args);
}
});
});
You can now use the MyValuePickerDatePicker in your code instead of the broken ValuePickerDatePicker.
<div id="dateSelectorDatePicker" data-dojo-type="myApp.dojox-fix.MyValuePickerDatePicker" data-dojo-props="slotOrder:[2,1,0]"></div>
A side effect is that you can't press and hold the +/- buttons if you want to scroll far; you will have to repeatedly press the button(s), but you can still type in a value, so it is not big a problem.

AngularJS re-initialise JS after statechange

I have social buttons from http://sharethis.com that I want to put on a profile page on my app. I'm using the ui-router for state management and page transitions, etc.
I've put the code I got from sharethis onto my index.html:
<script type="text/javascript" src="http://w.sharethis.com/button/buttons.js"></script>
<script type="text/javascript">stLight.options({publisher: "66053d76-64c6-4378-8971-aac043dbbc5d", doNotHash: false, doNotCopy: false, hashAddressBar: false});</script>
and the markup on my profiles page:
<span class='st_twitter_hcount' displayText='Tweet'></span>
<span class='st_facebook_hcount' displayText='Facebook'></span>
<span class='st_googleplus_hcount' displayText='Google +'></span>
What's happening is the social buttons are only loading when you refresh on the profile page (which is expected behaviour I guess). If the app bootstraps from say the homepage and then you transition to the profile page they aren't being loaded.
Are there any tricks that I can use to re-initialise or reload these javascript files?
I've spend some time investigating sharethis source code and found this function which creates buttons, on page controller add this function call and this will add the buttons.
stButtons.makeButtons();
While this might not be the best way according to documentation or best-practices, this was my solution. I had a particular view where I wanted a new set of ShareThis buttons to load. So I created a function which initialized the buttons again.
Here is the function which I placed in my controller:
$scope.loadShareThis = function() {
stButtons.makeButtons();
}
I then inititalize the function using ng-init in my html:
<div ng-init="loadShareThis()">
<share-buttons></sharebuttons>
</div>
This is probably not the answer, because you want to use shareThis, but I decided to use addthis, since I did not found a way to make shareThis work with angular.
Addthis enables asynchronous loading and dynamic rendering.
You can make it work like described here: http://support.addthis.com/customer/portal/articles/1293805-using-addthis-asynchronously.
And then you would probably want to make it into an angular directive, like described here: http://www.laurentiu-ciovica.com/2013/07/angularjs-addthis-directive.html
Answers by AlexG and Jeff Callahan gave me the direction, but it still didn't work for me. I had to wrap
stButtons.makeButtons()
in $timeout and now it works.
In HTML I have (JADE syntax)
my-directive(ng-repeat="review in myReviews" ng-init="loadShareThis()")
and in my controller I have
$scope.loadShareThis = function() {
$timeout(function(){
stButtons.makeButtons();
});
};
Has to do something with Angular digest cycles.

PrimeFaces p:blockUI blocking a certain component dynamically (on JSF EL condition)?

How do you make PrimeFaces p:blockUI block a certain component dynamically, that is on EL condition?
USE CASE:
The condition at hand is basically a mode that the user can be in on a page: if there are any exceeded collisions and they are currently shown at the users request then block the navigation tree (show exceeded collisions mode, nav tree blocked), otherwise we are in regular view and the navigation tree should be unblocked (show regular collisions mode, nav tree unblocked).
The problem currently is, that when in "exceeded mode", when a status change dialog changes the status to non-exceeded, the page renders/updates itself correctly back to regular mode, but the shadow on the navigation tree is still there. Since we're in regular mode now, I need some way to unblock if there are no more exceeded collisions.
Understood? :-)
OK, here's the bean property first:
/*
* The logic of this method ensures that after status update the
* mode is automatically put back into regular view if no followup
* date exceeded collisions exist.
*/
public boolean isFollowupExceededCollisionsShown()
{
return getFollowupExceededCount() > 0 ? this.followupExceededCollisionsShown : false;
}
1st try:
Looking at the VDL docs http://www.primefaces.org/docs/vdl/3.4/primefaces-p/blockUI.html revealed
some blocked attribute.
<p:blockUI widgetVar="explorerBlocker"
block=":explorer-form"
blocked="#{collisionManager.followupExceededCollisionsShown}" />
The above however does absolutely nothing.
2nd try:
Looking at the showcase http://www.primefaces.org/showcase/ui/blockUI.jsf, the client API seemed to be more adequate.
The idea was to call either show() or hide() depending on a ValueExpression when pressing OK on the status change dialog:
<p:dialog id="state-change-dialog"
widgetVar="stateChangeDialog"
modal="true"
appendToBody="true">
<h:form>
<ui:include src="/view/collisionmgmt/collisionStatusChangeGrid.xhtml" />
<h:panelGroup layout="block" styleClass="center-button-panel">
<p:commandButton id="save-button"
icon="ui-icon ui-icon-disk"
value="OK"
action="#{collisionManager.performStatusChange}"
process="#form"
update=":explorer-form:tree :collision-form:period-grid :collision-form:list :collision-form:growl"
oncomplete="stateChangeDialog.hide();" />
<p:commandButton icon="ui-icon ui-icon-close"
value="Cancel"
update=":collision-form:list"
onclick="stateChangeDialog.hide();"
immediate="true" />
</h:panelGroup>
</h:form>
</p:dialog>
The idea was to somehow extend the OK button's oncomplete="" with a call to explorerBlocker.show(); or explorerBlocker.hide(); depending on the new value of the EL #{collisionManager.followupExceededCollisionsShown}.
There are two basic variants I tried:
oncomplete="stateChangeDialog.hide(); #{collisionManager.followupExceededCollisionsShown ? 'explorerBlocker.show();' : 'explorerBlocker.hide();' }"
oncomplete="stateChangeDialog.hide(); if ( #{collisionManager.followupExceededCollisionsShown} ){ explorerBlocker.show(); } else { explorerBlocker.hide(); }"
The status change dialog is closed all the time, but the logic above isn't kicking in.
I must be making something inherently wrong here. I'm suspecting the OK button's oncomplete EL expression doesn't get re-evaluated when being clicked. Adding an #this to the update list doesn't change anything.
update="#this :explorer-form:tree :collision-form:period-grid :collision-form:list :collision-form:growl"
How is my problem best solved, where "best" is JSF-only first, then PrimeFaces-specific (and ideally, where this is documented!).
Is it possible to use the blockUI blocked="#{?}" attribute with EL?
Thanks
One way is to call hide() and show() method of BlockUI from Managed Bean.
You can do that by using RequestContext:
RequestContext.getCurrentInstance().execute("widgetVar.show()");
Another is you can pass the variable to JavaScript function and then let the Javascript function take care of that for you.
onClick="func(#{elvariable})"
<script type="text/javascript">
function func(value)
{
if(value==something){
widgetVar.show();
}else{
widgetVar.hide();
}
}
</script>

Ember Collapsible Container

I'm using Ember.js with handlebars and I need to make a div within my page collapse/expand when clicked. I know how to do this in jQuery, but I can't use any jQuery. Does anyone know how to accomplish this? Also I don't want to just toggle a hide attribute, I need the full sliding up and down feature for collapsing. If anyone has any ideas, I'd really appreciate it.
Thanks
Clicking on your view will cause a click event to be triggered. You can code your animation in any manner you want inside a click event handler in your view:
CollapsableView = Ember.View.extend({
click : function(event) {
this.$().toggle('fast');
}
})
The proper way of doing this in Ember is via the awesome Liquid Fire addon.
The outline:
Install Liquid Fire into your project.
Define a transition like this:
this.transition(
this.hasClass('transition-spoiler'),
this.toValue(true),
this.use('toDown'),
this.reverse('toUp')
);
In your controller/component, create a property spoilerIsVisible and a toggleSpoiler property:
spoilerIsVisible: false,
actions: {
toggleSpoiler: function() {
this.toggleProperty('spoilerIsVisible');
}
}
In your page/component template, create a button and a spoiler wrapper like this:
<button {{action 'toggleSpoiler'}}>
{{if spoilerIsVisible 'Show spoiler' 'Hide spoiler'}}
</button>
{{#liquid-if spoilerIsVisible class="transition-spoiler"}}
<p>Dumbledore dies</p>
{{/liquid-if}}
Note that you can wrap steps 3-4 into an x-spoiler component or something.
I do something similar, but with a tree-structure. I have written a blog post about this previously here: http://haagen-software.no/blog/post/2012-05-05-Ember_tree
It has the features you need in it, in that it adds and removed elements from the DOM when the nodes are clicked on.
A working example can be seen in an app I am currently building here: https://github.com/joachimhs/EurekaJ/tree/netty-ember/EurekaJ.View/src/main/webapp

AngularJS - Any way to call javascript after the template is linked?

I'm trying to use a jQuery plugin (Plupload) with AngularJS. I have created a directive that will be my file upload "widget". The directive looks like this (The code in the link function is a very simplified version of the example on the Plupload site):
.directive('myFileUpload', function () {
return {
restrict: 'E',
replace: true,
link: function(scope, element, attrs) {
scope.uploaderProperties = {
runtimes : 'html5,flash,html4',
url : 'media/upload',
max_file_size : '10mb',
container: 'fileUploadContainer',
drop_element: 'fileUploadDropArea'
};
scope.uploader = new plupload.Uploader(scope.uploaderProperties);
scope.uploader.init();
scope.uploader.bind('FilesAdded', function(up, files) {
scope.$apply();
});
},
templateUrl: 'upload.html'
};
});
My upload.html file looks like this:
<div id="{{uploaderProperties.container}}">
<div id="{{uploaderProperties.drop_element}}" style="border: 1px black dashed;">Drop files here<br><br><br></div>
Files to upload:<br>
<div ng-repeat="currFile in uploader.files">{{currFile.name}} ({{currFile.size}}) </div>
<br><br>
<!-- for debugging -->
{{uploader.files}}
<br><br>
</div>
When I include the directive on my page with a <my-file-upload> element, all the data bindings happen correctly. The problem is, when scope.uploader.init(); runs, the ids haven't been inserted into the DOM yet, and so Plupload complains and breaks since it can't select those elements. If I just hard-code the fileUploadContainer and fileUploadDropArea ids in the template, it works just fine. However, I'd really like to define those ids in only one place.
So, is there any way I can run the init() on the uploader after the template is linked in? I thought about using $timeout to delay when it runs, but that seems like a pretty big hack to me. Is there a more correct way of accomplishing this?
UPDATE
I wrapped the init() in a $timeout like this
$timeout(function() {
scope.uploader.init();
}, 2000);
just to make sure it would behave the way I was thinking, and sure enough, with this delay the plugin gets set up correctly and works. However, I do not want to have to rely on the timing of the $timeout. If there was just some way I could call scope.uploader.init(); after the template is linked in, everything should work fine. Anyone know of a good way of doing this?
Your problem is actually the other way around - The link function happens after the template is put in. So in this case, scope.uploaderProperties isn't set when the template happens.
It looks like your template is too fancy for the templateUrl option, really. You could try manually setting your ids with jQuery, or setting everything up in the compile function.
http://docs.angularjs.org/guide/directive

Categories

Resources