I want to toggle a buttons class depending on the state of a form in Angular. The template is based on Bootstrap.
I've setup a directive called IsDirty.
If the form has the class 'ng-valid', add the class 'btn-success' to the submit button.
Alternatively, if the form is dirty and has the class 'ng-dirty', remove the class 'btn-success' from the submit button.
So far this is what I have but it doesn't work.
var angular = require('angular');
angular.module('myApp')
.directive('isDirty', [function() {
return {
restrict: 'E',
link: function(scope, element, attrs, ctrl) {
var submitButton = element.find('.btn-primary');
if(element.hasClass('ng-valid')) {
submitButton.addClass('btn-success');
} else {
submitButton.removeClass('btn-success');
}
scope.$apply();
}
};
}]);
My form:
<form is-dirty class="form-horizontal" role="form" name="profileForm">
<!-- INPUT FIELDS HERE //-->
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="reset" class="btn btn-default">Cancel</button>
</form>
This should hopefully fix your problem
.directive('ngDirty', [function() {
return {
restrict: 'AE',
link: function(scope, element, attrs, ctrl) {
var submitButton = element[0].querySelector('.btn-primary');
if(element.hasClass('ng-valid')) {
submitButton.classList.add("btn-danger");
} else {
submitButton.classList.remove("btn-danger");
}
}
};
}]);
Plnkr Example
Update:
It's a little dirty but it seems to work and checks each input, you must bind each input to an ng-model though I have used $scope.input
New Plnkr
2nd Update
I have removed the function and brought in a $timeout you will see from the example how it works.
New update with a $timeout as function
Use ngClass for this (btw, I am confused with your class names. In your description you say add/remove the class .btn-success but in the code you are adding/removing .btn-danger. So in the code below, I am sticking with .btn-success):
<form is-dirty class="form-horizontal" role="form" name="profileForm">
<!-- INPUT FIELDS HERE //-->
<button type="submit"
class="btn btn-primary"
ng-class="{'btn-success' : isValid}">
Save Changes
</button>
<button type="reset" class="btn btn-default">Cancel</button>
</form>
And in your directive:
var angular = require('angular');
angular.module('myApp')
.directive('form', [function() {
return {
restrict: 'EA',
link: function(scope, element, attrs, ctrl) {
scope.isValid = element.hasClass('ng-valid');
}
};
}]);
I would further suggest that you actually make the class ng-valid itself with ng-class and use the variable scope.isValid to change between ng-valid and isDirty.
Related
I have the following code:
<template>
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-outline-dark active">
<input type="radio" name="grp" v-model="channel" value="vh1" checked> VH1
</label>
<label class="btn btn-outline-dark">
<input type="radio" name="grp" v-model="channel" value="mtv"> MTV
</label>
</div>
</template>
<script>
export default {
data() {
return {
channel: 'mtv'
}
},
watch: {
channel: function(newValue) {
console.log('value has been changed to ' + newValue);
}
}
}
</script>
When I click on a radio button, nothing happens. But when I removed the "data-toggle="buttons" attribute for the styling, then it starts working! Can anyone help me find a work around here?
Edit
For those of you who don't know, data-toggle is from bootstrap button plugin, it adds extra styles and functionality to the buttons you are making. Check out the docs here:
https://getbootstrap.com/docs/4.0/components/buttons/#toggle-states
Remove data-toggle and set active class by :class="{ 'active': channel === [value] }" for each input.
Finally got the solution
After long hours of searching, I was able to come up with a way to hack into the buttons.
Step 1
Change the above template to the following (removed v-model from the inputs)
<template>
<div class="btn-group" data-toggle="buttons" v-radio="channel">
<label class="btn btn-outline-dark active">
<input type="radio" name="grp" value="vh1" checked> VH1
</label>
<label class="btn btn-outline-dark">
<input type="radio" name="grp" value="mtv"> MTV
</label>
</div>
</template>
This solution was pretty difficult to find, and the solution was pretty hacky, but it got the job done.
Step 2 (UPDATED 6/18/2018)
Create a directive
In my case, since I was using single file components, I needed to bring it directives in the following way:
radio.js
export default {
inserted: function (el, binding) {
var btns = $(el).find('.btn');
var radioGrpName = $(btns[0]).find('input')[0].name;
$("input[name='" + radioGrpName + "'][value='" + binding.value + "']").closest('.btn').button('toggle');
},
bind: function (el, binding, vnode) {
var btns = $(el).find('.btn');
btns.each(function () {
$(this).on('click', function () {
var v = $(this).find('input').get(0).value;
(function set(obj, str, val) {
str = str.split('.');
while (str.length > 1) {
obj = obj[str.shift()];
}
return obj[str.shift()] = val;
})(vnode.context, binding.expression, v);
})
})
}
}
What this does is it binds a jquery click event to each radio button found in the div containing the v-radio. The second part where I'm doing function set (copied from the answer in reference), it checks to see if there's any dots in the expression, otherwise it sets the value of vnode.context["channel"], which updates the model. The bind hooks up to events before the component is loaded so it can be ready to fire off the internal workings of the radio button.
The inserted function is called after bind, during the physical insertion of the component into the parent node. So when you set an initial state (no event fired), the component will automatically reflect the value set in data.
Step 3
add directives radio to the script
import radio from '../../directives/radio'
<script>
export default {
directives: {
radio: radio
},
data() {
return {
channel: 'mtv'
}
},
//you can use computed property, but you need get and set
watch: {
channel: function(newValue) {
console.log('value has been changed to ' + newValue);
}
}
}
</script>
Using these links as references:
Old custom directive hack using vue 1
Update model from custom directive
I want to do a classic form submission from my Vue page, from a method. I don't want to use an <input type="submit">. How do I reference the form element in the page from my method? Surely I don't have to do document.getElementById() ?
markup
<div id="vueRoot">
<form>
<input name="vitalInformation" v-model="store.vital">
SUBMIT
</form>
</div>
code
var store = {vital:''};
vm = new Vue({
el : "#vueRoot",
data : {store : store},
methods : {
submit : function(){
//I'm ready, how do I do it ??
}
}
});
jsfiddle
The answer would be: ref: https://v2.vuejs.org/v2/api/#ref
Markup
<div id="vueRoot">
<form ref="form">
<input name="vitalInformation" v-model="store.vital">
SUBMIT
</form>
</div>
code
var store = {vital:''};
vm = new Vue({
el : "#vueRoot",
data : {store : store},
methods : {
submit : function(){
this.$refs.form.$el.submit()
}
}
});
Sorry for the late reply, but I am confused why you need ref when submitting a form.
data: function(){
return {
name: "",
price: "",
}
},
methods: {
addProduct(e){
e.preventDefault() // Prevent page from reloading.
// console.log(this.name, this.price);
}
}
<form v-on:submit="addProduct">
<input type="text" v-model="name" placeholder="Product Name" >
<input type="number" v-model="price" placeholder="Price">
<button type="submit">Add</button>
</form>
Another option is trigger the event click().
<button ref="submit" style="display:none;">Submit</button>
In your function call it as follows:
this.$refs.submit.click();
From above answers I used following statements
this.$refs.form.submit()
this.$refs.submit.click();
both throw following error
TypeError: Cannot read property '$refs' of null
I'm able to fix this issue by storing $refs in a variable then access the relevant method
if you want to use ref for form submit event
const form = this.$refs.formRef
form.submit()
if you want to use ref for other element's event like click
const btn = this.$refs.btnRef
btn.click()
In the case where it says that submit is not a function
It looks like if a form control has a name or id of submit it will mask the form's submit method.
In my case I had name="submit" on my submit button, I removed it and it worked fine.
I'm using the aurelia-dialog plugin to allow users to generate a set of objects, and want the dialog's response to return the chosen objects.
The workflow is that the list of options is generated from an API call using a promise when the activate() method is called on the dialog. The options are then displayed to the user, and selected from a dropdown. The user then clicks ok and the response should be sent back. Here is the code that is supposed to accomplish it:
this.ds.open({
viewModel: MyModal,
model: {
"title": "Select Objects",
"message": "I hate this modal"
}
}).then(response => {
console.log("closed modal");
console.log(response);
if (!response.wasCancelled) {
console.log('OK');
} else {
console.log('cancelled');
}
console.log(response.output);
});
And then in the modal.js:
import {inject} from 'aurelia-framework';
import {DialogController} from 'aurelia-dialog';
import {ModalAPI} from './../apis/modal-api';
//#inject(ModalAPI, DialogController)
export class MyModal {
static inject = [DialogController, ModalAPI];
constructor(controller, api){
this.controller = controller;
this.api = api;
controller.settings.centerHorizontalOnly = true;
}
activate(args){
this.title = args.title;
this.message = args.message;
this.returnedSet = null;
this.historicSetName = null;
this.reportHist = null;
return this.api.getReportHistory().then(reports => {
this.reportHist = reports;
});
}
selectHistoricReport() {
console.log(this.historicSetName);
if(this.historicSetName == "Select a report...") {
this.returnedSet = null;
} else {
var selectedReport = this.reportHist.filter(x => x.name == this.historicSetName)[0];
this.returnedSet = selectedReport.rsids;
}
console.log(this.returnedSet);
}
ok(returnedSet) {
console.log(returnedSet);
this.controller.ok(returnedSet);
}
}
And then the html:
<template>
<require from="../css/upload-set.css"></require>
<ai-dialog class="selector panel panel-primary">
<ai-dialog-header class="panel-heading">
<button type="button" class="close" click.trigger="controller.cancel()" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">${title}</h4>
</ai-dialog-header>
<ai-dialog-body class="panel-body container-fluid">
<div class="row">
<div class="col-sm-6">
<label>Report: </label>
<select value.bind="historicSetName" change.delegate="selectHistoricReport()" class="input-md form-control">
<option ref="historicSetPlaceholder">Select a report...</option>
<option repeat.for="historicReport of reportHist">${historicReport.name}</option>
</select>
</div>
</div>
</ai-dialog-body>
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Cancel</button>
<button click.trigger="ok(returnedSet)">Save</button>
</ai-dialog-footer>
</ai-dialog>
</template>
As long as I don't touch the dropdown, the dialog will return a null (or any other value I initialize returnedSet to). However, as soon as I click on the dropdown, clicking either the Save or Cancel button leads to nothing being returned and the console.log lines at the end of my first code block just get skipped. I also tried removing the click.delegate line from my HTML, but that didn't change anything.
Anyone have any idea why this might be happening?
Also, I found this post(Aurelia Dialog and Handling Button Events) with an extremely similar problem, but can't seem to find any solution in there as to what I should do.
Thanks in advance.
In angularjs I had the following:
app.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
And the html was:
<input type="text" ng-model="searchText" class="form-control"
placeholder="Search"
ng-enter="search($event, searchText)">
So basically once I have finished typing my text to search on, when I pressed the enter key the search function on my controller would run.
How would I do this in Aurelia?
I am still learning about its features so any help would be appreciated.
I think an alternative to the angular ngEnter would be:
import {customAttribute, inject} from 'aurelia-framework';
#customAttribute('enter-press')
#inject(Element)
export class EnterPress {
element: Element;
value: Function;
enterPressed: (e: KeyboardEvent) => void;
constructor(element) {
this.element = element;
this.enterPressed = e => {
let key = e.which || e.keyCode;
if (key === 13) {
this.value();//'this' won't be changed so you have access to you VM properties in 'called' method
}
};
}
attached() {
this.element.addEventListener('keypress', this.enterPressed);
}
detached() {
this.element.removeEventListener('keypress', this.enterPressed);
}
}
<input type="password" enter-press.call="signIn()"/>
The simplest way would be to wrap the input in a form element and bind to the submit event of the form.
<form role="form" submit.delegate="search()">
<div class="form-group">
<label for="searchText">Search:</label>
<input type="text" value.bind="searchText"
class="form-control" id="searchText"
placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
This will give you the same behavior you have built above. I'm still working to create an example of a custom attribute to do this, but honestly, this is how I would recommend you do this specific thing (capturing when the user presses enter).
This does not seem to be supported out of the box. While not perfect, here is what I intend to do:
Add a hidden submit button at the top of my form with a click.delegate attribute.
Run some code in my VM when its clicked. This code will decide what to do with the enter keypress based on any custom logic I need.
Hope that helps,
Andrew
EDIT:
You could also add a keypress event delegate:
<input keypress.delegate="doSomething($event)" />
And define doSomething() as:
doSomething(event) {
if(event.which == 13) {
alert('Your code goes here!');
}
event.preventDefault();
}
This will be a little cleaner when you have many inputs with differing enter keypress behaviours.
Because of keypress.delegate and keypress.trigger in an <input> block entering text by default, your function must return true to avoid it, like this:
doSomething(event) {
if(event.which == 13) {
console.log('Your code goes here!');
event.preventDefault();
return false;
}
return true;
}
At the moment I am working on a MVC4 view with multiple submit buttons. To handle the submit of the different buttons, I use this class:
http://blog.maartenballiauw.be/post/2009/11/26/Supporting-multiple-submit-buttons-on-an-ASPNET-MVC-view.aspx
I have three buttons and one label:
Start
Standby
Resume
How can I display a certain text in that label based on which button is pressed?
I wan to use Ajax.BeginForm to update the label text (so I do not have to reload the webpage).
Thank you in advance!
Update:
For example when I click at the Start Button a method will be executed. This method returns true or false. How to catch this bool and display text in the label, based on the result of the method?
Update 2:
<div>
<fieldset>
<legend>Admin Form</legend>
#Html.Label("Options")
<div id="StartBtn">
<input id="Start" type="submit" value="Start" name="action:Start" />
</div>
<div id="StandbyBtn">
<input id="Standby" type="submit" value="Standby" name="action:Standby" />
</div>
<div id="ResumeBtn">
<input id="Resume" type="submit" value="Resume" name="action:Resume" />
</div>
</fieldset>
</div>
[MultipleButton(Name = "action", Argument = "Start")]
public ActionResult Start()
{
if (start())
{
}
else
{
}
}
From your update I would use an ajax call instead of the ajax form
$('.btnSubmit').on('click', function(){
$.ajax({
url: '#Url.Action('Start', 'Controller')',
type: 'post',
data: {
button: this.id
}
dataType: 'json',
success: function(result){
if(result.Success){
$('.lblText').text(result.SetText);
}
}
});
});
I don't know what you want passed to your controller but if you put the same class on all of your buttons (you need to change them to type button instead of submit also) then this.id will will be the id of the clicked button and that will be sent to the controller
then on your controller have an input field matching what is in the data field
public ActionResult Start(string button){
//do something
//from here http://stackoverflow.com/questions/7732481/returning-json-from-controller-never-a-success
return Json(new { Success = "true", SetText = 'SetText' });
//Where SetText is whatever you want your label set to.
}
You can check on this post. http://www.developersnote.com/2013/02/multiple-button-in-mvc-4.html
#using (Html.BeginForm("ActionTaken", "TestController"))
{
<button name="button" value="ActionOne" class="button" style="width: 200px;">
test1</button>
<button name="button" class="button" style="width: 160px;" value="ActionTwo">
test2</button>
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ActionTaken(string butt)
{
string ButtCommand= butt;
switch (ButtCommand)
{
case "ActionOne":
//do stuff here
case "ActionTwo":
//do stuff here
default:
return View();
}
}