AngularJS : How to say to a directive to clone scope? - scope

I have this fiddle, and can not make this work. I believe that the reason resides in that two li elements with a custom directive edit-in-place share scope.
The solution would be to say to the directive to create a copy of the scope that binds on the parent - can transclude help?
angular.module('bla', [])
.directive('editInPlace', ['$parse','$compile', function($parse, $compile) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attribs) {
var inputStart = '<input style="border: 2 solid black" name="inPlaceInput" style="display:none" value="';
var inputEnd = '">';
scope.editModeAccessor = $parse(attribs.editInPlace);
scope.modelAccessor = $parse(attribs.ngBind);
scope.$watch(attribs.editInPlace, function(newValue, oldValue){
if (newValue){
console.debug("click");
console.debug("value: " + scope.modelAccessor(scope));
var inputHtml = inputStart + scope.modelAccessor(scope) + inputEnd;
element.after(inputHtml);
jQuery(element).hide();
scope.inputElement = jQuery("input[name=inPlaceInput]");
scope.inputElement.show();
scope.inputElement.focus();
scope.inputElement.bind("blur", function() {
blur();
});
} else {
blur();
}
});
function blur(){
console.debug("blur secondary");
if (scope.inputElement){
console.debug("blur secondary inputElement found");
var value = scope.inputElement.val();
console.debug("input value: "+ value);
scope.inputElement.remove();
jQuery(element).show();
scope.editModeAccessor.assign(scope, false);
scope.modelAccessor.assign(scope, value);
}
}
}
}
}]);
function ContactsCtrl($scope, $timeout){
$scope.contacts = [{number:'+25480989333', name:'sharon'},{number:'+42079872232', name:''}];
$scope.editMode = false;
var editedId;
$scope.edit = function(id){
$scope.editMode = true;
jQuery("#"+id).hide();
editedId = id;
//TODO show delete button
}
$scope.$watch('editMode', function(newValue, oldValue){
if (!newValue && editedId){
jQuery("#"+editedId).show();
}
});
}
<div ng-app="bla">
<div ng-controller="ContactsCtrl">
<h4>Contacts</h4>
<ul>
<li ng-repeat="contact in contacts">
<span edit-in-place="editMode" ng-bind="contact.number"></span>
<span edit-in-place="editMode" ng-bind="contact.name"></span>
<span id="{{$index}}" ng-click="edit($index)"><i class="icon-edit">CLICKtoEDIT</i></span>
</li>
</ul>
</div></div>

I think cloning the scope is not the best solution.
When creating a directive in angular, you should encapsulate all the functionality within the directive. You should also avoid mixing jQuery in when you don't have to. Most of the time (as in this case) you're just introducing unnecessary complexity. Lastly, classes are the best way of controlling display, rather than the style attribute on an element.
I took the liberty of rewriting your directive in a more "angular" way - with no jQuery. As you can see from the updated jsFiddle, it is simpler and cleaner. Also, it works!
This directive can be easily modified to add lots of additional awesome functionality.
app.directive( 'editInPlace', function() {
return {
restrict: 'E',
scope: { value: '=' },
template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
link: function ( $scope, element, attrs ) {
// Let's get a reference to the input element, as we'll want to reference it.
var inputElement = angular.element( element.children()[1] );
// This directive should have a set class so we can style it.
element.addClass( 'edit-in-place' );
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = function () {
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass( 'active' );
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access a native DOM function,
// we have to reference the first element in the array.
inputElement[0].focus();
};
// When we leave the input, we're done editing.
inputElement.prop( 'onblur', function() {
$scope.editing = false;
element.removeClass( 'active' );
});
}
};
});

Related

Input multiple with tags without autoCompletion

I have two inputs.
I want the two inputs to have the same look and feel see below:
The first input use autocomplete and allows the user to select a list of terms => I use p:autocomplete (see Primefaces documentation on autocomplete)
This input works fine.
For the second input, I would like to have the same display but without any autocompletion : the user just enter a list of terms with no autocompletion at all.
I tried to have a fake autocomplete that return the value given by the user but it is too slow and the behaviour is not correct when the user quit the input.
Any idea is welcome.
After a quick look at the PrimeFaces javascript code of the autoComplete and a few hours experimenting with it, I came up with a solution. It involves overriding the bindKeyEvents and in it deciding to call the original one or not, adding detection for the space key ('selecting a tag') and when pressed, add the tag and fire the selectionEvent (if ajax is used). Place the following code in your page or in an external javascript file
<script>
//<![CDATA[
if(PrimeFaces.widget.AutoComplete) {
PrimeFaces.widget.AutoComplete = PrimeFaces.widget.AutoComplete.extend ( {
bindKeyEvents: function() {
if (this.input.attr('data-justTags')) {
var $this = this;
this.input.on('keyup.autoComplete', function(e) {
var keyCode = $.ui.keyCode,
key = e.which;
}).on('keydown.autoComplete', function(e) {
var keyCode = $.ui.keyCode;
$this.suppressInput = false;
switch(e.which) {
case keyCode.BACKSPACE:
if ($this.cfg.multiple && !$this.input.val().length) {
$this.removeItem(e, $(this).parent().prev());
e.preventDefault();
}
break;
case keyCode.SPACE:
if($this.cfg.multiple) {
var itemValue = $this.input.val();
var itemDisplayMarkup = '<li data-token-value="' +itemValue + '"class="ui-autocomplete-token ui-state-active ui-corner-all ui-helper-hidden">';
itemDisplayMarkup += '<span class="ui-autocomplete-token-icon ui-icon ui-icon-close" />';
itemDisplayMarkup += '<span class="ui-autocomplete-token-label">' + itemValue + '</span></li>';
$this.inputContainer.before(itemDisplayMarkup);
$this.multiItemContainer.children('.ui-helper-hidden').fadeIn();
$this.input.val('').focus();
$this.hinput.append('<option value="' + itemValue + '" selected="selected"></option>');
if($this.multiItemContainer.children('li.ui-autocomplete-token').length >= $this.cfg.selectLimit) {
$this.input.css('display', 'none').blur();
$this.disableDropdown();
}
$this.invokeItemSelectBehavior(e, itemValue);
}
break;
};
});
} else {
//console.log("Original bindEvents");
this._super();
}
}
});
}
//]]>
</script>
For deciding on when to call the original one or not, I decided to use a passThrough attribute with a data-justTags name. e.g. pt:data-justTags="true" (value does not matter, so pt:data-justTags="false" is identical to pt:data-justTags="true"). A small html snippet of this is:
<p:autoComplete pt:data-justTags="true" multiple="true" value="#{myBean.selectedValues}">
And do not forget to add the xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" namespace declaration.
I found a component that could do the job : http://www.butterfaces.org/tags.jsf

Changing src attribute for an audio element doesn't work

Changing src attribute for an audio element doesn't work:
var Audio = React.createClass({
render : function() {
return (
<audio src={this.props.data.songUrl}/>
);
}
});
var Music = React.createClass({
render : function() {
return (
<article className="music">
<article className="musicContent">
<MusicButton data={Data} />
<List />
<Footer />
</article>
</article>
);
}
});
var MusicButton = React.createClass({
getInitialState : function() {
return {
isPlay : true,
count : 0
}
},
musicPlay : function () {
var audio = React.findDOMNode(this.refs.audio);
if(this.state.isPlay) {
audio.play();
this.setState({isPlay: false});
} else {
audio.pause();
this.setState({isPlay: true});
}
},
getBackWardMusic : function() {
this.setState({count: ++this.state.count});
var audio = React.findDOMNode(this.refs.audio);
audio.play();
},
getForwardMusic : function() {
this.setState({count: --this.state.count});
var audio = React.findDOMNode(this.refs.audio);
audio.play();
},
render : function() {
var classString = 'iconMusic icon-pause';
if(this.state.isPlay) {
classString = 'iconMusic icon-pause';
} else {
classString += ' rotate';
}
return (
<header className="musicHeader">
<Audio ref="audio" data={this.props.data[this.state.count]} />
<span onClick={this.getBackWardMusic} className="iconMusic icon-backward"></span>
<span onClick={this.musicPlay} className={classString}></span>
<span onClick={this.getForwardMusic} className="iconMusic icon-forward"></span>
</header>
);
}
});
after changing the source of audio you need to .load() first, before play() plays the new source.
you may like to use .oncanplaythroug = .play()
I don't have a specific answer for your question but I've found that media elements have their own lifecycle that I'm not sure is handled correctly in the React wrappers. The React and media element lifecycles have subtle interactions that are difficult to get right.
E.g., in Chrome, media elements don't release their resources unless you set src='' and if you do this in a React class, followed by a src='something-else' then I suspect the src='' can get optimised away.
To manage a video element, for example, I wrapped it in a React component and attached my own event listeners to the video DOM element to help manage its state and also managed cases like src='' and others by directly manipulating the DOM element in componentWillReceiveProps and componentWillUpdate based on what was changing.
Sorry I've not given a complete answer. It would take a lot of time to completely describe everything but I hope this helps a bit.

Does Knockout.mapping make ALL nested objects observable?

I am trying to map all possible nested objects of a JSON object so that each and every one is becomes an observable. I was under the impression that the use of ko.mapping.fromJS would result in all objects and their objects becoming observable. However, I am not seeing that happen.
If you look at the JSFiddle and code below you will see that the span initially displays the value "Test". My intention is for the button click to update the viewModel with the contents of stuff2, which should change the span's value to "Test2". However, the button click does not update anything.
http://jsfiddle.net/Eves/L5sgW/38/
HTML:
<p> <span>Name:</span>
<span data-bind="text: IntroData.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
JS:
var ViewModel = function (data) {
var me = this;
ko.mapping.fromJS(data, {}, me);
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};
return me;
};
var stuff = {
IntroData: {
Name: 'Test'
}
};
var stuff2 = {
IntroData: {
Name: 'Test2'
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(stuff));
ko.applyBindings(window.viewModel);
Is it just that I have to make use of mapping options to have the nested objects be made observable? If so, what if the JSON object is so vast and complex (this one obviously isn't)? Can some recursive functionality be used to loop through each object's nested objects to make them all observable?
Modifying the Update function as below will work.
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};

select2 plugin works fine when not inside a jquery modal dialog

I am using select2 plugin inside a jquery dialog but in does not work. When dropping down, the focus moves to the input control but immediately get out from it,not allowing me to type anything.
This is the HTML:
<div id="asignar_servicio" title="Asignar servicios a usuarios">
<input type="hidden" class="bigdrop" id="a_per_id" />
</div>
And this is the javascript code:
$( "#asignar_servicio" ).dialog({
autoOpen: false,
height: 500,
width: 450,
modal: true,
buttons: {
"Cancelar": function () {
$('#asignar_servicio').dialog('close');
}
}
});
$("#a_per_id").select2({
placeholder: "Busque un funcionario",
width: 400,
minimumInputLength: 4,
ajax: {
url: "#Url.Action("Search", "Personal")",
dataType: 'json',
data: function (term, page) {
return {
q: term,
page_limit: 10,
};
},
results: function (data, page) {
return { results: data.results };
}
}
}).on("change", function (e) {
var texto = $('lista_personal_text').val().replace(/ /g, '');
if (texto != '')
texto += ',';
texto += e.added.text;
var ids = $('lista_personal_id').val().replace(/ /g, '');
if (ids != '')
ids += ',';
ids += e.added.id;
});
I have this same code in other page and it works.
Any help will be appreciated,
thanks
Jaime
jstuardo's link is good, but there's a lot to sift through on that page. Here's the code you need:
$.ui.dialog.prototype._allowInteraction = function(e) {
return !!$(e.target).closest('.ui-dialog, .ui-datepicker, .select2-drop').length;
};
Just add it next to wherever you are setting the select2 drop down.
An easy way:
$.ui.dialog.prototype._allowInteraction = function (e) {
return true;
};
add this after whereever you set select2
Or try this from:
Select2 doesn't work when embedded in a bootstrap modal
Remove tabindex="-1" from the modal div
I have found this workaround. https://github.com/ivaynberg/select2/issues/1246
Cheers
Jame
There's a new version of the fix for select2 4.0 from the github issue thread about this problem:
if ($.ui && $.ui.dialog && $.ui.dialog.prototype._allowInteraction) {
var ui_dialog_interaction = $.ui.dialog.prototype._allowInteraction;
$.ui.dialog.prototype._allowInteraction = function(e) {
if ($(e.target).closest('.select2-dropdown').length) return true;
return ui_dialog_interaction.apply(this, arguments);
};
}
Just run this before any modal dialogs that will have select2 in them are created.
JSFiddle of this fix in action
The best solution I found was just making the dialog not be a modal dialog by removing modal:true. Once you do this the page will function as desired.
After a while of battling with this I found another option that allows you to keep the dialog as a modal. If you modify the css for select2 to something like the following:
.select2-drop {
z-index: 1013;
}
.select2-results {
z-index: 999;
}
.select2-result {
z-index: 1010;
}
keep in mind that this works however if you open a lot of dialogs on the same page it will eventually exceed the z-index specified, however in my use case these numbers got the job done.
Not enough reputation to comment on a previous post, but I wanted to add this bit of code:
$('#dialogDiv').dialog({
title: "Create Dialog",
height: 410,
width: 530,
resizable: false,
draggable: false,
closeOnEscape: false,
//in order for select2 search to work "modal: true" cannot be present.
//modal: true,
position: "center",
open: function () { },
close: function () { $(this).dialog("distroy").remove(); }
});
$("#displaySelectTwo")select2();
Updating to the newer version of JQuery and Select2 is not an option in our application at this time. (using JQueryUI v1.8 and Select2 v1)
Add this after your select2() declaration.
$.ui.dialog.prototype._allowInteraction = function (e) {
return !!$(e.target).closest('.ui-dialog, .ui-datepicker, .select2-dropdown').length;
};
I've used the following fix with success:
$.fn.modal.Constructor.prototype.enforceFocus = function () {
var that = this;
$(document).on('focusin.modal', function (e) {
if ($(e.target).hasClass('select2-input')) {
return true;
}
if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
that.$element.focus();
}
});
}
I could fix this by removing the option: 'modal: true' from the dialog options.
It worked fine.
For anyone stumpling upon this with Select2 v4.0.12
I was using the Select2 option dropdownParent
i set the dropDownParent value, and still had the issue.
dropdownParent: $("#ReportFilterDialog")
What fixed it for me, was setting the value to, to select the outer layer of the modal dialog:
dropdownParent: $("#ReportFilterDialog").parent()

Adding fields dynamically in JQuery-Jtable

How can I add fields dynamically in Jtable. I want to have multiple values for Cities
Please Refer the image attached
Thanks
Yes this is not built-in with jQuery jTable. To deal with this I've created a script for the same purpose. This handles (a) adding more controls OR group of controls and (b) remove control(s).
Here is the script:
//add '.add_more' class to
$(".add_more").on('click', function () {
// creates unique id for each element
var _uniqueid_ = +Math.floor(Math.random() * 1000000);
var new_ele_id = $(this).attr("data-clone-target") + _uniqueid_;
var cloneObj = $("#" + $(this).attr("data-clone-target"))
.clone()
.val('')
.attr("id", new_ele_id);
// if the control is grouped control
if ($(this).hasClass('group_control') == true) {
$($(cloneObj).children()).each(function () {
$(this).attr("id", $(this).attr("id") + _uniqueid_).val("");
});
}
$(cloneObj).insertBefore($(this));
//creates a 'remove' link for each created element or grouped element
$("<a href='javascript:void(0);' class='remove_this' data-target-id='" + new_ele_id + "'></a>")
.on('click', function () {
if ($(this).is(":visible") == true) {
if (confirm("Are you sure?")) {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}
else {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}).insertBefore($(this));
$("#" + new_ele_id).focus();
});
//remove element script
$(".remove_this").on('click', function () {
if ($(this).is(":visible") == true) {
if (confirm("Are you sure?")) {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
}
else {
$("#" + $(this).attr("data-target-id")).remove();
$(this).remove();
}
});
Usage: Single Element http://jsfiddle.net/vkscorpion1986/ktbn4qLg/2/
<input class="" id="<ELEMENT-ID>" type="text" name="input1">
Add More
Usage: Grouped Elements http://jsfiddle.net/vkscorpion1986/ktbn4qLg/4/
<div id="<ELEMENT-ID>">
<input class="" id="input1" type="text" name="input1">
<input class="" id="input2" type="text" name="input2">
</div>
Add More
attributes
href = javascript:void(0); // just to disable the anchor tag default behaviour
data-clone-target = id of the target element
css classes
.add_more = to implement the add more/remove controls functionality
.group_control = for indicating that this is group of elements which have to be repeted
Hope this works for you.
No, it's not made with jTable. You can use input option (http://jtable.org/ApiReference#fopt-input) and this: http://jqueryui.com/autocomplete/#multiple Or you can create your own dialog.

Resources