Edit In Place Content Editing - html

When using ng-repeat what is the best way to be able to edit content?
In my ideal situation the added birthday would be a hyperlink, when this is tapped it will show an edit form - just the same as the current add form with an update button.
Live Preview (Plunker)
HTML:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Custom Plunker</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"></script>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script src="app.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.0/css/bootstrap-combined.min.css"
rel="stylesheet">
</head>
<body ng-app="birthdayToDo" ng-controller="main">
<div id="wrap">
<!-- Begin page content -->
<div class="container">
<div class="page-header">
<h1>Birthday Reminders</h1>
</div>
<ul ng-repeat="bday in bdays">
<li>{{bday.name}} | {{bday.date}}</li>
</ul>
<form ng-show="visible" ng-submit="newBirthday()">
<label>Name:</label>
<input type="text" ng-model="bdayname" placeholder="Name" ng-required/>
<label>Date:</label>
<input type="date" ng-model="bdaydate" placeholder="Date" ng-required/>
<br/>
<button class="btn" type="submit">Save</button>
</form>
</div>
<div id="push"></div>
</div>
<div id="footer">
<div class="container">
<a class="btn" ng-click="visible = true"><i class="icon-plus"></i>Add</a>
</div>
</div>
</body>
App.js:
var app = angular.module('birthdayToDo', []);
app.controller('main', function($scope){
// Start as not visible but when button is tapped it will show as true
$scope.visible = false;
// Create the array to hold the list of Birthdays
$scope.bdays = [];
// Create the function to push the data into the "bdays" array
$scope.newBirthday = function(){
$scope.bdays.push({name:$scope.bdayname, date:$scope.bdaydate});
$scope.bdayname = '';
$scope.bdaydate = '';
};
});

You should put the form inside each node and use ng-show and ng-hide to enable and disable editing, respectively. Something like this:
<li>
<span ng-hide="editing" ng-click="editing = true">{{bday.name}} | {{bday.date}}</span>
<form ng-show="editing" ng-submit="editing = false">
<label>Name:</label>
<input type="text" ng-model="bday.name" placeholder="Name" ng-required/>
<label>Date:</label>
<input type="date" ng-model="bday.date" placeholder="Date" ng-required/>
<br/>
<button class="btn" type="submit">Save</button>
</form>
</li>
The key points here are:
I've changed controls ng-model to the local scope
Added ng-show to form so we can show it while editing
Added a span with a ng-hide to hide the content while editing
Added a ng-click, that could be in any other element, that toggles editing to true
Changed ng-submit to toggle editing to false
Here is your updated Plunker.

I was looking for a inline editing solution and I found a plunker that seemed promising, but it didn't work for me out of the box. After some tinkering with the code I got it working. Kudos to the person who made the initial effort to code this piece.
The example is available here http://plnkr.co/edit/EsW7mV?p=preview
Here goes the code:
app.controller('MainCtrl', function($scope) {
$scope.updateTodo = function(indx) {
console.log(indx);
};
$scope.cancelEdit = function(value) {
console.log('Canceled editing', value);
};
$scope.todos = [
{id:123, title: 'Lord of the things'},
{id:321, title: 'Hoovering heights'},
{id:231, title: 'Watership brown'}
];
});
// On esc event
app.directive('onEsc', function() {
return function(scope, elm, attr) {
elm.bind('keydown', function(e) {
if (e.keyCode === 27) {
scope.$apply(attr.onEsc);
}
});
};
});
// On enter event
app.directive('onEnter', function() {
return function(scope, elm, attr) {
elm.bind('keypress', function(e) {
if (e.keyCode === 13) {
scope.$apply(attr.onEnter);
}
});
};
});
// Inline edit directive
app.directive('inlineEdit', function($timeout) {
return {
scope: {
model: '=inlineEdit',
handleSave: '&onSave',
handleCancel: '&onCancel'
},
link: function(scope, elm, attr) {
var previousValue;
scope.edit = function() {
scope.editMode = true;
previousValue = scope.model;
$timeout(function() {
elm.find('input')[0].focus();
}, 0, false);
};
scope.save = function() {
scope.editMode = false;
scope.handleSave({value: scope.model});
};
scope.cancel = function() {
scope.editMode = false;
scope.model = previousValue;
scope.handleCancel({value: scope.model});
};
},
templateUrl: 'inline-edit.html'
};
});
Directive template:
<div>
<input type="text" on-enter="save()" on-esc="cancel()" ng-model="model" ng-show="editMode">
<button ng-click="cancel()" ng-show="editMode">cancel</button>
<button ng-click="save()" ng-show="editMode">save</button>
<span ng-mouseenter="showEdit = true" ng-mouseleave="showEdit = false">
<span ng-hide="editMode" ng-click="edit()">{{model}}</span>
<a ng-show="showEdit" ng-click="edit()">edit</a>
</span>
</div>
To use it just add water:
<div ng-repeat="todo in todos"
inline-edit="todo.title"
on-save="updateTodo($index)"
on-cancel="cancelEdit(todo.title)"></div>
UPDATE:
Another option is to use the readymade Xeditable for AngularJS:
http://vitalets.github.io/angular-xeditable/

I've modified your plunker to get it working via angular-xeditable:
http://plnkr.co/edit/xUDrOS?p=preview
It is common solution for inline editing - you creale hyperlinks with editable-text directive
that toggles into <input type="text"> tag:
<a href="#" editable-text="bday.name" ng-click="myform.$show()" e-placeholder="Name">
{{bday.name || 'empty'}}
</a>
For date I used editable-date directive that toggles into html5 <input type="date">.

Since this is a common piece of functionality it's a good idea to write a directive for this. In fact, someone already did that and open sourced it. I used editablespan library in one of my projects and it worked perfectly, highly recommended.

Related

adding element to parent from directive

In order to disable the auto complete of chrome in forms, i want to create angular directive which add dummy input element
my code is
`angular.module("noc.components").directive('customInput', function ($compile) {
"use strict";
return {
restrict:'E',
template:'<input>',
replace:true,
link: function(scope, elem) {
//scope.type = attrs.type || 'email';
var el = angular.element('<input name={{type}} style="display: none">');
//var type = elem[0].name;
$compile(el)(scope);
elem.parent().append(el);
}
};
});`
and the html is
<div class="form-group">
<label for="setupWizardUserStepEmail">Email Address</label>
<custom-input class="form-control"
id="setupWizardUserStepEmail"
ng-model="setupRequest.userRequest.email"
name="email"
ng-required="true"
noc-validation="email"
autocomplete="off"
autofocus="true">
</div>
however the injected html isnt in the parent - i want it to be above the directive
how can i do it?
I was able to solve my own problem in the end:
I changed it to elem.parent().prepend(el);

Angularjs : Data binding with HTML generated from a variable

I already apologize for asking, just beginning with angularjs (and client-side dev) and I am trying to do something pretty simple which does not work.
I have some HTML that I want to generate from a variable. This html contains a ng-model directive that I want to bind with some data. Basically if I put directly the html inside the html file, data binding works fine:
HTML
<form role="form" ng-submit="search()" ng-controller="AppController">
<div class="form-group">
<label for="search__origin">Departure</label>
<input type="text" class="form-control" id="search__origin" ng-model="routes[0].origin" /> {{routes[0].origin}}
</div>
</form>
Anjularjs
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.routes = {}
var route_format = {
origin: 'SG',
departure_date: new Date(),
return_date: new Date()
};
$scope.routes['0'] = route
})
Fiddle here
But if I put my HTML (containing a "ng-model" directive) into a $scope variable, then use "ng-bind-html" directive to generate the html directly into the html template, then data binding does not work:
HTML
<form role="form" ng-submit="search()" ng-controller="AppController">
<div class="form-group">
<label for="search__origin">Departure</label>
<div ng-bind-html=myhtml class="form-group"></div>
{{routes[0].origin}}
</div>
</form>
angularjs
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope,$sce) {
$scope.routes = {}
var route_format = {
origin: 'SG',
departure_date: new Date(),
return_date: new Date()
};
$scope.routes['0'] = route_format;
var html = '<input type="text" class="form-control" id="search__origin" ng-model="routes[0].origin"/>'
var getTrustedHtml = function(unsafe_html) {
var x = $sce.trustAsHtml(unsafe_html);
return x;
}
$scope.myhtml = getTrustedHtml(html);
})
Fiddle here
I am wondering here why it does not work, is it because I process in the wrong order and that when the html is generated on the page, the relation with the $scope variable cannot be done?
I would appreciate some help here :), thanks in advance!
Al
ng-bind-html will not compile directives for you.
you need either use $compile service and compile this fragment yourself that is ugly
or
restructure the code not to have directives inside ng-bind
Thanks STEVER for giving me the hint, appreciated!
I have solved my issue using the directive 'ng-include' and creating a html template. It also allows me to iterate ('ng-repeat') over this html template with dynamic 'ng-model's as below:
HTML
<form role="form" ng-submit="search()" ng-controller="AppController">
<div class="form-group">
<div ng-repeat="i in range(route_nb) track by $index">
<div ng-include="'test.html'"></div>
</div>
</div>
{{routes[0].origin}}
{{routes[1].origin}}
</form>
<script type="text/ng-template" id="test.html">
<div class="form-group">
<label for="search__origin">Departure</label>
<input type="text" class="form-control" id="search__origin" ng-model="routes[$index].origin" />
</div>
</script>
jsangular
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.routes = {};
$scope.route_nb = 2;
$scope.routes['0'] = {
origin: 'SG',
departure_date: new Date(),
return_date: new Date()
};
$scope.routes['1'] = {
origin: 'LON',
departure_date: new Date(),
return_date: new Date()
};
$scope.range = function(n) {
return new Array(n);
};
})
Fiddle
However I am wondering whether it is really efficient in term of performances...

Angular Checkboxes "Select All" functionality with only one box selected initially

I have a form that contains 3 checkboxes: "Select All", "Option 1", and "Option 2".
<form id="selectionForm">
<input type="checkbox" ng-model="selectAll" >Select all
<br>
<input type="checkbox" ng-checked="selectAll" checked>Option 1
<br>
<input type="checkbox" ng-checked="selectAll">Option 2
</form>
On the initial page load I want only Option 1 to be checked. And then if the Select All checkbox gets checked it should automatically check Option 1 and Option 2 so all are selected.
The problem is on the initial page load the ng-checked="selectAll" gets evaluated which overrides my attempt to initially check only Option 1 (selectAll = false initially), so nothing is selected.
This seems like a simple problem to solve, but I can't figure out a solution... Thanks in advance for any insights or advice!
Another way to go about it is to use a model for the options, set default selection in the model and have your controller handle the logic of doing select all.
angular.module("app", []).controller("ctrl", function($scope){
$scope.options = [
{value:'Option1', selected:true},
{value:'Option2', selected:false}
];
$scope.toggleAll = function() {
var toggleStatus = !$scope.isAllSelected;
angular.forEach($scope.options, function(itm){ itm.selected = toggleStatus; });
}
$scope.optionToggled = function(){
$scope.isAllSelected = $scope.options.every(function(itm){ return itm.selected; })
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"> </script>
<div ng-app="app" ng-controller="ctrl">
<form id="selectionForm">
<input type="checkbox" ng-click="toggleAll()" ng-model="isAllSelected">Select all
<br>
<div ng-repeat = "option in options">
<input type="checkbox" ng-model="option.selected" ng-change="optionToggled()">{{option.value}}
</div>
</form>
{{options}}
</div>
Try this:
<form id="selectionForm">
<input type="checkbox" ng-model="selectAll" >Select all
<br>
<input type="checkbox" ng-checked="selectAll || option1" ng-init="option1=true" ng-model="option1">Option 1
<br>
<input type="checkbox" ng-checked="selectAll">Option 2
</form>
I like to use an ng-repeat for clarity on showing what you're selecting/un-selecting, basically you end up with a nice little object to base it all on, and adding to it is just easier.
Here's a Plunker
*Also notice how you can achieve allSelected? with a loop func and not a ton of html, and I'm sure this can be done with less spaghetti but it works *
app.controller('MainCtrl', function($scope) {
$scope.allSelected = false;
$scope.checkboxes = [{label: 'Option 1',checked: true}, {label: 'Option 2'}}}];
$scope.cbChecked = function(){
$scope.allSelected = true;
angular.forEach($scope.checkboxes, function(v, k) {
if(!v.checked){
$scope.allSelected = false;
}
});
}
$scope.toggleAll = function() {
var bool = true;
if ($scope.allSelected) {
bool = false;
}
angular.forEach($scope.checkboxes, function(v, k) {
v.checked = !bool;
$scope.allSelected = !bool;
});
}
});

How to change class of particular input in angularjs

I want to create function (in angularjs controller) which add particular class to the parent div of the empty input when clicking on the button. And remove that class when i type something in the input.
I have a working example here: http://jsfiddle.net/fUdLv/
HTML:
<div ng-app="authorization">
<div ng-controller="authCtrl as auth">
<div ng-class="{'error':auth.needEmail}">
<input type="text" ng-model="auth.email" ng-keypress="auth.clearEmail()">
</div>
<div ng-class="{'error':auth.needPassword}">
<input type="password" ng-model="auth.password" ng-keypress="auth.clearPassword()">
</div>
<div>
<button ng-click="auth.signin()">Sign in</button>
</div>
</div>
</div>
Javascript:
angular.module('authorization', [])
.controller('authCtrl', function() {
this.needEmail = false;
this.needPassword = false;
this.signin = function pay() {
if (!this.email){
this.needEmail = true;
}
if (!this.password){
this.needPassword = true;
}
};
this.clearEmail = function(){
this.needEmail = false;
}
this.clearPassword = function(){
this.needPassword = false;
}
});
But i'm sure it is very bad code, because i have a particular function for working with particular input. How can i generalize this function for working on every input (for example, if i would have three or four inputs it is not very smart solution to create function for each of them)
How about use <form> and FormController to control state?
You probably need to read this guide from official site.

Using ZeroClipboard on a Bootstrap Modal

I have a Twitter Bootstrap modal that I am displaying where I want to include a "Copy to Clipboard" button. I am attempting to use the ZeroClipboard component https://github.com/jonrohan/ZeroClipboard
Below is my sample code. Button "Copy1" is on a page directly and that works. Button "Copy2" is on the modal and that is not working. Internet Explorer appears to "lock up" when "Copy2" is pressed.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<link type="text/css" rel="stylesheet" href="/Content/bootstrap.min.css" />
<script src="/Scripts/jquery-1.9.1.min.js"></script>
<script src="/Scripts/ZeroClipboard.min.js"></script>
<script src="/Scripts/bootstrap.min.js"></script>
</head>
<body>
<div class="row">
<div class="span6">
<!-- Textbox and copy button pair #1 (not on modal) -->
<input type="text" id="Input1" />
<button class="btn" type="button" id="Copy1" data-clipboard-target="Input1">Copy Input #1</button>
</div>
<div class="span6">
<a class="btn" type="button" href="#Modal1" data-toggle="modal">Show Modal Dialog</a>
</div>
</div>
<div class="modal hide fade" role="dialog" id="Modal1">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>Copy to Clipboard Modal</h3>
</div>
<div class="modal-body">
<p>
<!-- Textbox and copy button pair #2 (on modal) -->
<input type="text" id="Input2" />
<button class="btn" type="button" id="Copy2" data-clipboard-target="Input2">Copy Input #2</button>
</p>
</div>
<div class="modal-footer">
<p>
<button class="btn" type="button" data-dismiss="modal">Close</button>
</p>
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
ZeroClipboard.setDefaults({
moviePath: '/Scripts/ZeroClipboard.swf'
});
var clip1 = new ZeroClipboard($("#Copy1"));
var clip2 = new ZeroClipboard($("#Copy2"));
});
</script>
</body>
</html>
Can anyone offer some guidance on what is going wrong? I understand that Bootstrap takes the modal out of the DOM tree until displayed, but I'm not sure how to accommodate for that.
Edit: Corrected id of 2nd input to be "Input2" to match the button's target.
In addition, I have attempted the following javascript:
//var clip2 = new ZeroClipboard($("#Copy2"));
$("#Modal1").on('shown', function () {
var clip2 = new ZeroClipboard($("#Copy2"));
});
Also, it would appear that the issue is browser-specific.
The original code and my modified code locks up Internet Explorer 10. But Google Chrome is OK under both code attempts.
Your 2nd copy button is targeting a nonexistent ID:
<button class="btn" type="button" id="Copy2" data-clipboard-target="Input2">Copy Input #2</button>
Your markup is incorrect:
<input type="text" id="Text2" />
Should be:
<input type="text" id="Input2" />
When we use "ZeroClipboard" with "Bootstrap modal", it creates conflict in "IE".
We need to make changes in bootstrap's "js" file.
There is method defined for, “ Modal.prototype.enforceFocus “ element in “Bootstrap.js” .
Here, in “If” condition, we need to add following condition,
!$(e.target).closest('[id ^= "ZeroClipboardMovie_"]').length)
Resultant code will look as follows,
if (this.$element[0] !== e.target && !this.$element.has(e.target).length && !$(e.target).closest('[id ^= "ZeroClipboardMovie_"]').length) {
this.$element.focus()
}
where, [id ^= "ZeroClipboardMovie_"] is ZeroClipboard container element.
In above code, it is taking container of recent "ZeroClipboard". You can change it to, appropriate "zeroclipboard-container" as per the requirement.
You can also checkout this blog post for more detail- http://wisdmlabs.com/blog/ie-issue-zeroclipboard-bootstrap-modal/
if (!checkIsBelowIE8()) {
if (/MSIE|Trident/.test(window.navigator.userAgent)) {
(function ($) {
var zcClass = '.' + ZeroClipboard.config('containerClass');
var proto = $.fn.modal.Constructor.prototype;
proto.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') /* Guard against infinite focus loop */
.on('focusin.bs.modal', $.proxy(function (e) {
if (this.$element[0] !== e.target &&
!this.$element.has(e.target).length &&
/* Adding this final condition check is the only real change */
!$(e.target).closest(zcClass).length
) {
this.$element.focus();
}
}, this));
};
})(window.jQuery);
}
var client = new ZeroClipboard($(".clipboard"));
client.on("copy", function (e) {
var clipboard = e.clipboardData;
clipboard.setData("text/plain", $.trim($(e.target).parent().prev().find("input").val()));
showMsg("success", "copy success");
});
}
function checkIsBelowIE8() {
try {
var b_name = navigator.appName;
var b_version = navigator.appVersion;
var version = b_version.split(";");
var trim_version = version[1].replace(/[ ]/g, "");
if (b_name == "Microsoft Internet Explorer") {
/*if IE6 or IE7*/
if (trim_version == "MSIE8.0" || trim_version == "MSIE7.0" || trim_version == "MSIE6.0") {
return true;
} else {
return false;
}
} else {
return false;
}
} catch (e) {
return false;
}
}
hope to help you