I have simple test app, I want to remove and add tags, I have code like this:
<script id="tags_template" type="text/x-jsrender">
<div class="tags">
Tags:
<ul>
{^{for tags}}
<li>{{:name}}<a>×</a></li>
{{/for}}
<li><input /></li>
</ul>
</div>
</script>
and JS
var $view = $('#view');
var tags_tmpl = $.templates("#tags_template");
var tags = [];
tags_tmpl.link('#view', {tags: tags});
$view.find('input').keydown(function(e) {
if (e.which == 13) {
$.observable(tags).insert({name: $(this).val()});
$(this).val('');
}
});
$view.find('ul').on('click', 'a', function() {
// how to remove the tag?
});
Now how can I remove the tag? There is $.observable(array).remove but how can I reference that element in template and how can I get it in javascript?
Yes, your own answer is correct. But you may be interested in using a more data-driven and declarative approach, as follows:
<div id="view"></div>
<script id="items_template" type="text/x-jsrender">
Items (Hit Enter to add):
<ul>
{^{for items}}
<li>
{{:name}}
<a class="remove" data-link="{on ~remove}">×</a>
</li>
{{/for}}
</ul>
<input data-link="{:newName trigger=true:} {on 'keydown' ~insert}"/>
</script>
And
var items_tmpl = $.templates("#items_template");
var items = [];
items_tmpl.link('#view', {items: items}, {
insert: function(ev) {
if (ev.which === 13) {
// 'this' is the data item
$.observable(items).insert({name: this.newName});
$.observable(this).setProperty('newName', '');
}
},
remove: function() {
// 'this' is the data item
$.observable(items).remove($.inArray(this, items));
}
});
Alternatives for remove would be:
remove: function(ev) {
var view = $.view(ev.target);
$.observable(items).remove(view.index);
}
Or:
remove: function(ev, eventArgs) {
var view = $.view(eventArgs.linkCtx.elem);
$.observable(items).remove(view.index);
}
http://jsfiddle.net/BorisMoore/f90vn4mg/
BTW new documentation for {on ... event binding coming soon on http://jsviews.com
Found it in the docs:
$view.find('ul').on('click', 'a', function() {
var view = $.view(this);
$.observable(tags).remove(view.index);
});
Related
I am looking for a way to synchronize arrays within an AngularJS controller.
Example:
var input = [1];
var synchArray = DatabindToModifiedInput()
// synchArray is something like this:
// [{name:someObject}, {name:inputElement, Id:1}]
input.push(2);
// synchArray should be updated automatically:
// [{name:someObject}, {name:inputElement, Id:1}, {name:inputElement, Id:2}]
Obviously i could register $watches and modify synchArray when input changes but that doesn't feel very angular-like.
Question:
I am tempted to write a filter which i can apply to the input-array. However this still feels like i am missing some obvious way to bind the data together within a controller/service.
Is there some way to utilize ngRepeat or some databinding-mechanism for this? Or should i maybe approach this in a completely different way?
You should likely make an extended array object as demonstrated in this post by Jacob Relkin.
That way you could do more than just one array or event when something happens.
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.myClonedArray = [];
$scope.myExtendedArray;
// Extended array type
function EventedArray(handler) {
this.stack = [];
this.mutationHandler = handler || function() {};
this.setHandler = function(f) {
this.mutationHandler = f;
};
this.callHandler = function(event, obj) {
if (typeof this.mutationHandler === 'function') {
this.mutationHandler(event, obj);
}
};
this.push = function(obj) {
this.stack.push(obj);
this.callHandler('push', obj);
};
this.pop = function() {
var obj = this.stack.pop();
this.callHandler('pop', obj);
return obj;
};
this.getArray = function() {
return this.stack;
}
}
var handler = function(event, item) {
console.log(event, item);
if (event === 'push') {
$scope.myClonedArray.push(item);
} else if (event === 'pop') {
$scope.myClonedArray.pop();
}
};
$scope.myExtendedArray = new EventedArray(handler);
//or
// $scope.myExtendedArray = new EventedArray();
// $scope.myExtendedArray.setHandler(handler);
$scope.addItem = function() {
$scope.myExtendedArray.push($scope.inputValue);
};
$scope.popItem = function() {
$scope.myExtendedArray.pop();
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<html ng-app="myApp">
<body ng-controller="MyCtrl">
<input type="text" ng-model="inputValue" />
<button ng-click="addItem()">Add</button>
<button ng-click="popItem()">Pop</button>
<p>Custom Array</p>
<ul>
<li ng-repeat="item in myExtendedArray.stack track by $index">
{{item}}
</li>
</ul>
<p>Cloned Array</p>
<ul>
<li ng-repeat="item in myClonedArray track by $index">
{{item}}
</li>
</ul>
</body>
</html>
I am trying to call a JavaScript function from the onclick event of two different buttons. I have dug around and searched for like problems but have not found a solutions. When I click either button I get the error
Error: 'RemoveCode' is undefined'
What am I doing wrong?
<script type="text/javascript">
$(document).ready(function ()
{
function RemoveCode(codeType)
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
});
</script>
Code for my buttons:
<li>
<label for="SelectedProjects">Selected Projects:</label>
<select size="1" id="SelectedProjects" name="SelectedProjects" multiple></select> <button class="removeButton" onclick="RemoveCode('Project')" type="button">-</button>
</li>
<li>
<label for="SelectedTasks">Selected Tasks:</label>
<select size="1" multiple id="SelectedTasks" name="SelectedTasks"></select> <button class="removeButton" onclick="RemoveCode('Task')" type="button">-</button>
</li>
I should note that on the same page there are multiple change events for the other elements on the page and they all work fine. It is just this `onclickP that is failing.
Firstly note that in your if condition you need to use == (not =) to compare values.
To solve your issue you have two options. Firstly you could simply move the RemoveCode function out of the scope of the document.ready handler so that it can be accessed from the onclick attribute:
<script type="text/javascript">
function RemoveCode(codeType)
{
// your code...
}
$(document).ready(function ()
{
// your code...
});
</script>
Alternatively, it would be much better practice to add your event handlers using unobtrusive Javascript. As you're using jQuery, here's how you can do that:
$(function() {
$('button').click(function() {
var $selectedProjectsField = $("#SelectedProjects");
var $selectedProjectCodesField = $("#SelectedProjectCodes");
var $selectedTasksField = $("#SelectedTasks");
var $selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if ($(this).data('codetype') == "Project") {
selectedOption = $selectedProjectsField.find(':selected').index();
} else {
selectedOption = $selectedTasksField.find(':selected').index();
}
alert(selectedOption);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
<label for="SelectedProjects">Selected Projects:</label>
<select size="1" id="SelectedProjects" name="SelectedProjects" multiple></select>
<button class="removeButton" data-codetype="Project" type="button">-</button>
</li>
<li>
<label for="SelectedTasks">Selected Tasks:</label>
<select size="1" multiple id="SelectedTasks" name="SelectedTasks"></select>
<button class="removeButton" data-codetype="Task" type="button">-</button>
</li>
</ul>
You are defining your RemoveCode method inside a closure. This function will thus not be available as onclick callbacks of your HTML elements.
You can just update your code to this and it should work:
<script type="text/javascript">
function RemoveCode(codeType)
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
</script>
put your function out side of document.ready()
<script type="text/javascript">
$(document).ready(function () // No Need of this Function here
{ });
function RemoveCode(codeType) // Automatically load when Your page is getting loaded on Browser.
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
</script>
You are defining your ready() method inside of a closure.
You then have two approaches you can use. First is you can not use $(document).ready() as the buttons that call ready() can't be clicked until the document is ready anyway.
Second is you could bind the onclick inside of your $(document).ready().
$(document).ready(function() {
$('#firstItem').click(function() { Ready('Project'); });
....
});
I'm using knockout.js (v. 3.2.0) to create a series of checkbox lists on a page. For any given list I want checked items to move to the top of the list, and move below the checked items when unchecked.
At first I thought I could use a js function to reorder the list and then call the function after ko.applyBindings which is apparently synchronous (calling it before applyBindingds wouldn't work because the DOM isn't complete yet). Nevertheless, it didn't work. Any idea how to write a function in the view model that will do this?
Here is my markup:
<ul data-bind="foreach: targetingViewModel.filteredTargetingInstances('assets')">
<li>
<label>
<input type="checkbox" data-bind="checked:selected"/>
<span data-bind="text: value"></span>
</label>
</li>
</ul>
And here is the jQuery function I found in another Stack Overflow answer:
addSelectionsToList = function () {
var $list = $(".target-list");
sortItems($list);
function sortItems(list){
var origOrder = list.children();
list.on("click", ":checkbox", function() {
var i,
checked = document.createDocumentFragment(),
unchecked = document.createDocumentFragment();
for (i = 0; i < origOrder.length; i++) {
if (origOrder[i].getElementsByTagName("input")[0].checked) {
checked.appendChild(origOrder[i]);
} else {
unchecked.appendChild(origOrder[i]);
}
}
list.append(checked).append(unchecked);
});
}
};
Thanks.
Check out following Code snippet. hope this is will help you.
Steps I have performed
Created one viewmodel with class Item with subscribe on change of
isChecked property.
There is Obsservable array of list items which
will contain isChecked property.
Finally on change of value it
will call the sorting by selecting value on change in any item.
function SomeViewModel() {
var self = this;
self.chkLists = ko.observableArray([]);
function Item(isChecked, name) {
this.isChecked = ko.observable(isChecked);
this.name = ko.observable(name);
this.isChecked.subscribe(function (newValue) {
self.chkLists.sort(function (l) { return l.isChecked() === false });
})
}
self.chkLists = ko.observableArray([
new Item(false, "Bear"),
new Item(false, "Hippo"),
new Item(false, "Lion"),
new Item(false, "Tiger"),
new Item ( false, "Zebra" )
]);
}
$(document).ready(function () {
var divEle = $("#sortedList")[0];
ko.applyBindings(SomeViewModel, divEle);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="sortedList">
<ul data-bind="foreach: chkLists">
<li>
<label>
<input type="checkbox" data-bind="checked: $data.isChecked" />
<span data-bind="text: $data.name"></span>
</label>
</li>
</ul>
</div>
I have a selectable:
<ol id="selectable">
<li class="ui-widget-content">1</li>
<li class="ui-widget-content">2</li>
<li class="ui-widget-content">3</li>
</ol>
I want to capture every selected item body into a hidden input separated by a comma, so after selecting some items it would look for example like this:
<input type="hidden" id="bad_times" name="bad_times" value="1,3" />
where 1,3 are bodies of the items selected. Any examples from the web I tried failed to work. Please note that only selected items have to be captured, if I select some item, then unselect, then select again it should appear only once. How to achieve it?
Following assumes that jQuery UI selectable plugin is being used
If so you can try something like this and build on it
$(function() {
$("#selectable").selectable({
filter: "li" ,
unselected:mapSelected,
selected:mapSelected
});
});
function mapSelected(event,ui){
var $selected = $(this).children('.ui-selected');
var text = $.map($selected, function(el){
return $(el).text()
}).join();
$('#bad_times').val(text)
}
DEMO
What have you tried so far and where were you running into issues?
Based on the docs the selected items have the class 'ui-selected'
So you should just be able to iterate over the selected items something like:
var str = "";
$( ".ui-selected").each(function(i) {
if (i > 0)
str += ",";
str += $(this).text();
});
$('#bad_times').val(str);
I would be in favor of using a data attribute, say, data-value and using an array, [1,3], instead of a list 1,3.
Special Note: The demo and code below simply help to verify the concept and do not use the selectable plugin.
HTML:
<input type="hidden" id="bad_times" name="bad_times" data-value="[]" />
JS:
$(function() {
var hidden = $('#bad_times');
$('#selectable li').on('click', function() {
var val = +$(this).text();
hidden.data()['value'].indexOf(val) > -1 || hidden.data()['value'].push(val);
console.log( hidden.data()['value'] );
});
});
$(function() {
var hidden = $('#bad_times');
$('#selectable li').on('click', function() {
var val = +$(this).text();
hidden.data()['value'].indexOf(val) > -1 || hidden.data()['value'].push(val);
$('pre.out').text( JSON.stringify( hidden.data()['value'] ) );
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ol id="selectable">
<li class="ui-widget-content">1</li>
<li class="ui-widget-content">2</li>
<li class="ui-widget-content">3</li>
</ol>
<input type="hidden" id="bad_times" name="bad_times" data-value="[]" />
<pre class="out"></pre>
My html page is :
<div data-role="content">
<div id="menu">
<ul id="menu" data-role="listview" class="ui-listview "
data-bind="foreach: menu">
<li>
<a data-bind="text:name, attr: {href: urlmenu}"></a>
<a href="#" data-bind="{ click: $parent.remove }"
data-role="button" data-icon="delete"></a>
</li>
</ul>
</div>
</div>
<div data-role="footer" data-position="fixed">
<div data-role="navbar">
<ul id="footer">
<li>Home</li>
<li>Asignaturas</li>
<li>Mensajes</li>
</ul>
</div>
</div>
And my JS code is :
$( document ).on( "pagebeforechange" , function(e, data) {
var toPage = data.toPage[0].id;
if( toPage == "home"){
ko.cleanNode(document.getElementById('menu'));
menu();
}
});
function menuViewModel(){
var self = this;
self.menu = ko.observableArray([]);
self.menu.removeAll();
self.menu = ko.observableArray([
new EditMenuViewModel("Perfil"),
new EditMenuViewModel("Asignaturas")
]);
}
function EditMenuViewModel(name) {
this.name = ko.observable(name);
this.urlmenu = ko.observable("#"+name);
};
function menu(){
var menuviewModel = new menuViewModel();
ko.applyBindings(menuviewModel, document.getElementById('menu'));
}
When I load my page for the first time everything works fine, but when I click on link footer home, the array content is duplicated.
Example is here:
Any idea?
Thanks
You have two DOM elements with id=menu, a div and a ul.
<div id="menu"> <!-- <-- change this id for example -->
<ul id="menu" data-role="listview" class="ui-listview "
data-bind="foreach: menu">
...
</ul>
</div>
Ids should be unique, you need to change the id on one of your elements, hopefully this will also solve your problem.
Update
As you can read in this thread, ko.cleanNode will not remove items created using foreach binding.
You need to change your approach.
Here is a jsFiddle that reproduces your problem.
What you can do is stop cleaning+applying bindings, and update your observableArray instead:
$( document ).on( "pagebeforechange" , function(e, data) {
var toPage = data.toPage[0].id;
if( toPage == "home"){
menuviewModel.menu.removeAll(); //clear menu
//add whatever menu item you need
menuviewModel.menu.push(new EditMenuViewModel("New Menu1 " + (new Date()).getTime()));
menuviewModel.menu.push(new EditMenuViewModel("New Menu2 " + (new Date()).getTime()));
}
});
function menuViewModel(){
var self = this;
self.menu = ko.observableArray([]);
self.menu.removeAll();
self.menu = ko.observableArray([
new EditMenuViewModel("Perfil"),
new EditMenuViewModel("Asignaturas")
]);
}
function EditMenuViewModel(name) {
this.name = ko.observable(name);
this.urlmenu = ko.observable("#"+name);
};
//bind only once
var menuviewModel = new menuViewModel();
ko.applyBindings(menuviewModel, document.getElementById('menu'));
Here is an example
This is old thread, but I've found a (ugly) way to overcome it:
before the cleaning, I'm caching the observable array values and set it with only 1 value. After the rebind, I'm restoring the cached values. Something like this:
var self = this;
self.myArray = ko.observableArray(['val1', 'val2']);
var tempArray = [];
self.BeforeCleaning = function () {
tempArray = self.myArray()
self.myArray(['temp value']);
};
self.AfterRebinding = function () {
self.myArray(tempArray);
};
horrible, but works for me.