Single edit hyperlink option for all the field edits - javascript

I have created an application in AngularJS with edit, save and cancel options, the application is working fine, right now i am having edit option for all the three fields, but how can i have a single edit option for making all the fields editable and save and cancel similarly
Can anyone please tell me some solution for this
DEMO
HTML
<div ng-controller="LocationFormCtrl">
<h2>Editors</h2>
<span ng-repeat="location in locations">
<div class="field">
<strong>State:</strong>
<div click-to-edit="location.state"><input ng-model="location.state"/></div>
</div>
<div class="field">
<strong>City:</strong>
<div click-to-edit="location.city">
<select ng-model="location.city" ng-options="loc.city as loc.city for loc in selectableCities"></select>
</div>
</div>
<div class="field">
<strong>Neighbourhood:</strong>
<div click-to-edit="location.neighbourhood"><input ng-model="location.neighbourhood"/></div>
</div>
<hr></hr>
</span>
</div>

I created a new directive that uses you directive. Also i updated your directive a bit.
Not sure if you wanted this.
you can find the jsFiddle demo here.
updated your directive so that it does not show individual save n cancel when in edit all mode.
you can find the updated jsFiddle demo here.
app.directive("clickToEditAll",function(){
var partial = '<div><div class="field">' +
'<strong>State:</strong>' +
'<div click-to-edit="myData.state" external-controle="externalControle"><input ng-model="myData.state"/></div>' +
' </div>' +
'<div class="field">' +
'<strong>City:</strong>' +
'<div click-to-edit="myData.city" external-controle="externalControle">' +
'<select ng-model="myData.city" ng-options="loc.city as loc.city for loc in selectableCities"></select>' +
'</div>' +
'</div>' +
'<div class="field">' +
'<strong>Neighbourhood:</strong>' +
'<div click-to-edit="myData.neighbourhood" external-controle="externalControle"><input ng-model="myData.neighbourhood"/></div>' +
'</div>' +
'<button ng-show="externalControle.editAll !== true" ng-click="editAllClicked()">Edit All</button><button ng-show="externalControle.editAll === true" ng-click="saveAllClicked()">Save</button><button ng-show="externalControle.editAll === true" ng-click="cancelAllClicked()">Cancel</button><hr/></div>'
var retValue = {
restrict: "A",
template:partial,
scope:{
myData:"=",
selectableCities:"=",
externalControle: "="
},
link:function(scope, elements, attributes){
scope.editAllClicked = function(){
scope.externalControle.editAll = true;
}
scope.saveAllClicked = function(){
scope.externalControle.saveAll = true;
}
scope.cancelAllClicked = function(){
scope.externalControle.cancelAll = true;
}
}
}
return retValue;
});

Related

ng-if is not working inside ng-repeat in when bind-html-compile

I have the below code to bind html in my html page,
<div bind-html-compile="menuButtonView"></div>
I have the below code in my controller,
dashboardService.getTemplateMetaData(data.templateCategory)
.success(function(data) {
console.log(data);
$scope.buttonArray = data.btnArray;
$scope.firstMenuLabel = data.firstMenuLabel;
if (data.firstMenuLabel) {
$scope.menuButtonView = '<obl-menu-group label="{{firstMenuLabel}}" icon="fa-pencil-square-o">' +
'<div data-ng-repeat="btn in buttonArray" >' +
'<div ng-if="btn.menuTitle !=="Site Settings" ">' +
'<obl-menu-button label="{{btn.menuTitle}}" icon="fa fa-file-image-o" menu-function="{{btn.menuFunction}}">' +
'</obl-menu-button></div>' +
'<div ng-if="btn.menuTitle ==="Site Settings"">' +
'<obl-menu-group label="{{btn.menuTitle}}" icon="fa-pencil-square-o" class="md-sub-menu">' +
'<obl-menu-button label="Contact Us" icon="fa fa-file-image-o" click-title="contactUs"></obl-menu-button>' +
'</obl-menu-button></div>' +
'</div>' +
'<obl-menu-group>';
}
}).error(function(err) {
});
}).error(function(err) {
});
The ng-if inside the ng-repeat is not working. I can't see anything wrong in this code. Is there a problem with the quotation marks?
use single quote in ng-if
'<div data-ng-repeat="btn in buttonArray" >' +
'<div ng-if="btn.menuTitle !=='Site Settings'">'+
//Your code
</div>'
'<div ng-if="btn.menuTitle ==='Site Settings'">'+
//Your code
</div>'
'</div>'

Label for not working in angularjs directive

I am using this custom template inside a directive to generate customized input fields with associated labels:
template: function(elem, attrs) {
var elemTpl = '<div> ' +
'<div class="form-group col-md-{{cols}}" >' +
'<label for="{{id}}" class="control-label input-sm">{{text}}</label> ' +
'<input type="{{type}}" ng-model="value" name="' + attrs.id + '" id="' + attrs.id +
'" class="form-control input-sm szpFocusable" placeholder="{{placeholder}}" ng-required="required" spellcheck="false"/>' +
'</div> ' +
'</div>';
return elemTpl;
}
The problem is that when the label is clicked, the input field is not being focused. When moving the code outside the directive and putting the input tags directly in the html, all works fine. The id and the name attributes are bound correctly to the appropriate scope fields.
Are you sure the ID you are using is unique?
Another option is to nest the <input> tag in the <label>, in that case you don't need the for argument:
var elemTpl = '<div> ' +
'<div class="form-group col-md-{{cols}}" >' +
'<label class="control-label input-sm">{{text}}' +
'<input type="{{type}}" ng-model="value" name="' + attrs.id + '" id="' + attrs.id + '" class="form-control input-sm szpFocusable" placeholder="{{placeholder}}" ng-required="required" spellcheck="false"/>' +
'</label>' +
'</div> ' +
'</div>';
Note:
The real answer is in the 2nd comment below: In a directive the content of element template is placed within the directive tag, in this case resulting in the id being used twice. This can be solved by adding replace : true to the return value of the directive.
This will replace your directive tag with the contents of your template.
app.directive('myDirective', function() {
return {
replace: 'true',
template: templateThatUsesId
};
});
Here there is a more accurate answer:
Attribute of label not working in angular directive?
When rendering directives with templates the container has the same id than the checkbox:
<div class="color-warning checkbox ng-isolate-scope ng-valid" ng-model="ckeck4" id="kitten>
<input id="kitten" type="checkbox">
<label for="kitten">CUSTOM LABEL</label>
</div>
The div that wrappers the rendered directive and the checkbox have the same id, not quite good.
In order to delete it we use a link function:
link: function(scope,el,attrs){
el.removeAttr("id")
}
This is because the link function is executed before angular renders the directive, so, we are removin the id from the div that wrappers the directive, here there is my complete code:
(function() {
'use strict';
angular.module('planetCheckbox', [])
.controller('PlanetCheckboxController', PlanetCheckboxController)
.directive('checkbox', DirectiveCheckbox);
function DirectiveCheckbox() {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '=',
classes: '#class',
customLabel: '#',
check: '=',
id:'#'//,
//radio :'#'
},
link: function(scope,el,attrs){
el.removeAttr("id")
},
controller: 'PlanetCheckboxController',
templateUrl: 'planet-checkbox/template.html'
}
};
PlanetCheckboxController.$inject = ['$scope'];
function PlanetCheckboxController($scope) {
$scope.ngModel = $scope.check;
};
angular.module('planetCheckbox')
.run(['$templateCache', function ($templateCache) {
$templateCache.put('planet-checkbox/template.html', '<div class="checkbox">'+
'<input id="{{id}}" type="checkbox" />' +
'<label for="{{id}}">CUSTOM LABEL</label>' +
'</div>'
);
}]);
})();
The living example is here: http://jsfiddle.net/cherniv/5u4Xp/
Label for is working for id and not for name
<label for="idxyz" class="control-label input-sm">{{text}}</label>
<input id="idxyz" type="{{type}}" ng-model="value" .....
It always works for me!

A way to share common template HTML and JavaScript behavior across AngularJS directives?

I'm building a form builder AngularJS app, and I have the following AngularJS directive representing the UI to edit a TextField:
angular.module('myApp.directives').directive('textFormFieldElement', ['$timeout', function($timeout) {
'use strict';
return {
restrict: 'A',
scope: {
element: '='
},
template: '<div class="form-element text-form-field">' +
' <span class="identifier">TF</span>' +
' <strong class="heading" ng-bind="headingText()"></strong>' +
' <div class="editor">' +
' <div class="form-group">' +
' <label>Enter the field name:</label>' +
' <input ng-model="element.fieldName" type="text" class="form-control" />' +
' </div>' +
' <div class="form-group">' +
' <label>Enter a label for the field:</label>' +
' <input ng-model="element.label" type="text" class="form-control" />' +
' </div>' +
' <div class="form-group">' +
' <label>Enter a note for the field:</label>' +
' <input ng-model="element.note" type="text" class="form-control" />' +
' </div>' +
' <div class="checkbox">' +
' <label>' +
' <input ng-model="element.required" type="checkbox" /> Required' +
' </label>' +
' </div>' +
' </div>' +
'</div>',
link: function(scope, element, attributes) {
scope.element.fieldName = scope.element.fieldName || 'TextField';
// Expand the editor when creating new elements, and focus on the first field once rendered.
if (!scope.element._id) {
$timeout(function() {
element.find('.editor').addClass('expanded');
element.find('.editor').find('input:first').select();
}, 10);
}
scope.headingText = function() {
if (scope.element.fieldName.length && scope.element.fieldName.length > 0) {
return scope.element.fieldName;
}
return 'TextField';
};
}
};
}]);
I also have other controls, like a PhoneField, an EmailField, and a RadioButtonListField.
These directives will often have common HTML in their templates as well as common JavaScript behavior. I'd like a way to share this across directives, without polluting the global namespace.
What are some ways this can be achieved?
For template sharing you could extract common template code into 'template directives', i.e. directives that only define template without behavior. This way you can re-use these template directives inside the templates of other directives.
For sharing common JavaScript behavior the two recommended ways are either via Angular services (service/factory) or in the case of directives using directive controller.
For the latter, see the Creating Directives that Communicate section at http://docs.angularjs.org/guide/directive on how to implement this.

Event works only one time

I set event listener to button and expect new node adding every time the button is pressed, but, in fact, this works only one time. That must be my mistake with ":last" selector which somehow freezes on the same node. How can I fix this?
$("#btnAddWord").on('click', function(){
$('div.input-group:last').after( inputGroup );
});
http://jsfiddle.net/aT82W/
It's because you try to add again and again the same element (instead of a new one). Let's create a new one for each click.
Modified code from your JSFiddle example:
$("#btnAddWord").on('click', function(){
var inputGroup = document.createElement('div');
inputGroup.setAttribute('class','input-group');
inputGroup.innerHTML = '...here your HTML...';
$('div.input-group:last').after( inputGroup );
});
You should use a function that generates new input groups, here you're always referring to the same element (created one time only).
function createInputGroup() {
return $('<div class="input-group">' +
'<span class="input-group-addon">' +
' <input type="checkbox">' +
'</span>' +
'<input type="text" class="form-control" placeholder="New word">' +
'<span class="input-group-addon">' +
' <span class="glyphicon glyphicon-remove-circle"></span>' +
'</span>' +
'</div>');
}
$("#btnAddWord").on('click', function(){
$('div.input-group:last').after( createInputGroup() );
});
Looking at your fiddle link it seems that, inputGroup is no longer available on next click. so try this:
function CreateDiv()
{
var inputGroup = document.createElement('div');
inputGroup.setAttribute('class','input-group');
inputGroup.innerHTML = '<span class="input-group-addon">'+
' <input type="checkbox">'+
'</span>'+
'<input type="text" class="form-control" Placeholder="New word">'+
'<span class="input-group-addon">'+
' <span class="glyphicon glyphicon-remove-circle"></span>'+
'</span>';
return inputGroup;
}
$(document).on("click", "#btnAddWord", function(){
$('div.input-group:last').after( CreateDiv() );
});
$("#btnAddWord").on('click', function(){
var inputGroup = document.createElement('div');
inputGroup.setAttribute('class','input-group');
inputGroup.innerHTML = '<span class="input-group-addon">'+
' <input type="checkbox">'+
'</span>'+
'<input type="text" class="form-control" Placeholder="New word">'+
'<span class="input-group-addon">'+
' <span class="glyphicon glyphicon-remove-circle"></span>'+
'</span>';
$('div.input-group').last().append( inputGroup );
});
you are just moving the same element from place to place (which is the same place by the way).
You need to clone this element by doing
$(inputGroup).clone()

Customizing the template within a Directive

I have a form that is using markup from Bootstrap, like the following:
<form class="form-horizontal">
<fieldset>
<legend>Legend text</legend>
<div class="control-group">
<label class="control-label" for="nameInput">Name</label>
<div class="controls">
<input type="text" class="input-xlarge" id="nameInput">
<p class="help-block">Supporting help text</p>
</div>
</div>
</fieldset>
</form>
There's a lot of boilerplate code in there, that I'd like to reduce to a new directive - form-input, like follows:
<form-input label="Name" form-id="nameInput"></form-input>
generates:
<div class="control-group">
<label class="control-label" for="nameInput">Name</label>
<div class="controls">
<input type="text" class="input-xlarge" id="nameInput">
</div>
</div>
I have this much working via a simple template.
angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
scope: {
label: 'bind',
formId: 'bind'
},
template: '<div class="control-group">' +
'<label class="control-label" for="{{formId}}">{{label}}</label>' +
'<div class="controls">' +
'<input type="text" class="input-xlarge" id="{{formId}}" name="{{formId}}">' +
'</div>' +
'</div>'
}
})
However it's when I come to add in more advanced functionality that I'm getting stuck.
How can I support default values in the template?
I'd like to expose the "type" parameter as an optional attribute on my directive, eg:
<form-input label="Password" form-id="password" type="password"/></form-input>
<form-input label="Email address" form-id="emailAddress" type="email" /></form-input>
However, if nothing is specified, I'd like to default to "text". How can I support this?
How can I customize the template based on the presence / absence of attributes?
I'd also like to be able to support the "required" attribute, if it's present.
Eg:
<form-input label="Email address" form-id="emailAddress" type="email" required/></form-input>
If required is present in the directive, I'd like to add it to the generated <input /> in the output, and ignore it otherwise. I'm not sure how to achieve this.
I suspect these requirements may have moved beyond a simple template, and have to start using the pre-compile phases, but I'm at a loss where to start.
angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
element.replaceWith(htmlText);
}
};
})
Tried to use the solution proposed by Misko, but in my situation, some attributes, which needed to be merged into my template html, were themselves directives.
Unfortunately, not all of the directives referenced by the resulting template did work correctly. I did not have enough time to dive into angular code and find out the root cause, but found a workaround, which could potentially be helpful.
The solution was to move the code, which creates the template html, from compile to a template function. Example based on code from above:
angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
template: function(element, attrs) {
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
return htmlText;
}
compile: function(element, attrs)
{
//do whatever else is necessary
}
}
})
The above answers unfortunately don't quite work. In particular, the compile stage does not have access to scope, so you can't customize the field based on dynamic attributes. Using the linking stage seems to offer the most flexibility (in terms of asynchronously creating dom, etc.) The below approach addresses that:
<!-- Usage: -->
<form>
<form-field ng-model="formModel[field.attr]" field="field" ng-repeat="field in fields">
</form>
// directive
angular.module('app')
.directive('formField', function($compile, $parse) {
return {
restrict: 'E',
compile: function(element, attrs) {
var fieldGetter = $parse(attrs.field);
return function (scope, element, attrs) {
var template, field, id;
field = fieldGetter(scope);
template = '..your dom structure here...'
element.replaceWith($compile(template)(scope));
}
}
}
})
I've created a gist with more complete code and a writeup of the approach.
Here's what I ended up using.
I'm very new to AngularJS, so would love to see better / alternative solutions.
angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
scope: {},
link: function(scope, element, attrs)
{
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
element.html(htmlText);
}
}
})
Example usage:
<form-input label="Application Name" form-id="appName" required/></form-input>
<form-input type="email" label="Email address" form-id="emailAddress" required/></form-input>
<form-input type="password" label="Password" form-id="password" /></form-input>

Categories

Resources