KnockoutJS accordion like behaviour with custom binding - javascript

I'm trying to build some accordion like functionality with KnockoutJS and custom bindings
So far I've got it half way there:
<div data-bind="foreach: items">
<div class="item"> <span data-bind="text: name() ? name() : 'New Thing'"></span>
<br /> Show / Hide
<div class="fullDetails" data-bind="accordion: visible">
<label>Name:</label>
<input type="text" data-bind="value: name" />
</div>
</div>
</div>
Add new item
and my Javascript
var ViewModel = function () {
var self = this;
self.items = ko.observableArray([new ItemModel()]);
self.addItem = function (item) {
ko.utils.arrayForEach(self.items(), function (i) {
i.visible(false);
});
self.items.push(new ItemModel())
};
self.toggleVisibility = function (item) {
ko.utils.arrayForEach(self.items(), function (i) {
i.visible(false);
});
item.visible(!item.visible())
};
};
var ItemModel = function () {
this.visible = ko.observable(true);
this.name = ko.observable();
}
ko.bindingHandlers.accordion = {
init: function (element, valueAccessor) {
var value = valueAccessor();
$(element).toggle(ko.utils.unwrapObservable(value));
},
update: function (element, valueAccessor) {
var value = valueAccessor();
ko.utils.unwrapObservable(value) ? $(element).slideDown() : $(element).slideUp();
}
};
ko.applyBindings(new ViewModel());
Also on JSFiddle:
http://jsfiddle.net/alexjamesbrown/PPAsK/
Some issues I am having:
Show/Hide link
This seems to scroll up and then immediately scroll down again.
Adding a new item
I was trying to get the item currently visible to slideUp, then the new item to slideDown but this isn't working - whatever item is visible slidesUp, but the new item just appears

Issue 1:
you are always showing on show/hide link click because the item previously gets reset to visible(false). Fix:
ko.utils.arrayForEach(self.items(), function (i) {
//don't bother with the item, we take care of it later
if (item !== i) {
i.visible(false);
}
});
Issue 2:
you need to start with the added item hidden, then apply self.toggleVisibility(newItem)
This will take care of hiding the others and animating the new one
self.addItem = function (item) {
var newItem = new ItemModel();
//make sure it starts hidden
newItem.visible(false);
self.items.push(newItem);
//highlight it the usual way
self.toggleVisibility(newItem);
};
fixed version here:
http://jsfiddle.net/tudorilisoi/3LzbT/8/

Related

Knockout custom handler to hide textbox value if it is disabled

I have a form that basically has a handful of properties that are shared between a few items. When you select the radio button for the item the text boxes enable for data entry, only one item can be selected at a time.
I have everything setup and working except I do not want the bound values to display in the textbox if the control is disabled. I have been trying to work with the handlers but I am having a hell of a time trying to understand how to make things work the way I need. I have looked at many articles by Ryan and the custom handlers he has provided but I need an epiphany, but until then I am seeking your help. Also, is there a more appropriate way to handle the IsEnabled function I have created or is that the best way?
Here is the JSFiddle
Updated JSFiddle, instead of doing the value I am attempting to create a custom handler that disabled and deletes the value. It kinda works but it stops after a few updates and the value doesn't get updated.
Here is some sample HTML:
<ul>
<li>
<input type="radio" name="item" value="1" data-bind="checked:Selected" /> Item 1 <input type="text" data-bind="value:Price, enable:IsEnabled('1')" />
</li>
<li>
<input type="radio" name="item" value="2" data-bind="checked:Selected" /> Item 2 <input type="text" data-bind="value:Price, enable:IsEnabled('2')" />
</li>
<li>
<input type="radio" name="item" value="3" data-bind="checked:Selected" /> Item 3 <input type="text" data-bind="enabledValue:Price, enable:IsEnabled('3')" />
</li>
<li>
<input type="radio" name="item" value="4" data-bind="checked:Selected" /> Item 4 <input type="text" data-bind="enabledValue:Price, enable:IsEnabled('4')" />
</li>
</ul>
Here is the sample JS:
var vm = {
Selected: ko.observable('1'),
Price: ko.observable(12),
IsEnabled: function(item){
var selected = this.Selected();
return (selected == item)
}
}
ko.applyBindings(vm);
(function (ko, handlers, unwrap, extend) {
"use strict";
extend(handlers, {
enabledValue: {
init: function (element, valueAccessor, allBindings) {
var bindings = allBindings();
var enabled = ko.unwrap(bindings.enable);
var value = unwrap(valueAccessor());
if (enabled)
handlers.value.init();
},
update: function (element, valueAccessor, allBindings) {
var bindings = allBindings();
var enabled = ko.unwrap(bindings.enable);
var value = unwrap(valueAccessor());
handlers.value.update(element,function() {
if(enabled)
return valueAccessor(value);
});
}
}
});
}(ko, ko.bindingHandlers, ko.utils.unwrapObservable, ko.utils.extend));
Tony. I've just simplified your sample and got it working with sharing same value property between different items. The main idea that a binding will store internal computed and will bind an element against it.
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
var showValue = ko.computed({
read: function(){
if (unwrap(allBindings().enable)) {
return unwrap(valueAccessor());
} else {
return '';
}
},
write: valueAccessor()
});
ko.applyBindingsToNode(element, { value: showValue });
}
}
});
http://jsfiddle.net/7w566pt9/4/
Note that in KO 3.0 ko.applyBindingsToNode is renamed to ko.applyBindingAccessorsToNode.
But wouldn't it have more sense to make the bindings remember last entered value for each item? It's quite simple to implement.
Update
Remembering last edited value for the particular item is similar in the manner that you should keep that value internally like showValue. Let's name it lastValue:
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
// Create observable `lastValue` with some default content.
// It will be created for EVERY binding separately.
var lastValue = ko.observable(0);
// If an item is currently enabled then set `lastValue` to the actual value.
if (unwrap(allBindings().enable)) lastValue(unwrap(valueAccessor()));
// This piece will be executed only once (for the selected item) and other
// items will store default value in `lastValue`!
// It's the internal anonymous computed intended to update bound
// price to reflect currently edited value.
ko.computed(function(){
if (unwrap(allBindings().enable)) valueAccessor()(lastValue());
});
// Note that passed function will be triggered whenever item is enabled
// and/or `lastValue` changes.
// Here we just change valueAccessor() to `lastValue`.
var showValue = ko.computed({
read: function(){
if (unwrap(allBindings().enable)) {
return lastValue();
} else {
return '';
}
},
write: lastValue
});
ko.applyBindingsToNode(element, { value: showValue });
}
}
});
http://jsfiddle.net/7w566pt9/8/
I hope it is nearly what you expected. Usually in such cases the real problem is not implementing a feature but describing how the feature should work.
Since my additions to the answer have been edited out I have added this answer to help those new to KO.
Here is a KO 3.0 implementation using ko.applyBindingAccessorsToNode.
extend(handlers, {
enableValue: {
init: function (element, valueAccessor, allBindings) {
var showValue = ko.computed({
read: function () {
if (unwrap(allBindings().enable)) {
return valueAccessor(); // CHANGED
} else {
return '';
}
},
write: valueAccessor //CHANGED
});
ko.applyBindingAccessorsToNode(element, { value: showValue }); //CHANGED
}
}
});
As stated in the release notes there is no official documentation for it yet but this is what I was able to put together. I used the group message to determine the differences. Hopefully this will save someone time until it has more documentation.
Release Notes
ko.applyBindingsToNode is superseded by
ko.applyBindingAccessorsToNode. The second parameter takes an object
with pairs of bindings and value-accessors (functions that return the
binding value). It can also take a function that returns such an
object. (This interface isn't currently documented on the website.)
Group Message from Michael Best stating it is better.
Compatibility Issue

MVC Two ListBox's Adding from first to second on doubleclick

I have two ListBoxes that in my view. At the minute I have it working so that when one of the two buttons are pressed then the selected element is moved to the other list box.
I would like to in addition to that, have it so that if one of the elements is Doubleclicked it moves to the other listbox.
This is the code in the view
<div class="PageRegionRole">
#Html.Label("Available Roles", new { id = "Label1" })
#Html.ListBoxFor(m => m.SelectedRoleID, new SelectList(Model.StaffRoleList, "Value", "Text"), new { id = "LeftBox" })
<div>
<input type="button" id="left" value="<<" />
<input type="button" id="right" value=">>" />
</div>
#Html.Label("Current Roles", new { id = "Label2" })
#Html.ListBoxFor(model => model.SelectedRoleID, Model.StaffRole, new { id = "RightBox" })
</div>
This is the code i have in the js file for it
$(function () {
$("#left").bind("click", function () {
var options = $("[id*=RightBox] option:selected");
for (var i = 0; i < options.length; i++) {
var opt = $(options[i]).clone();
$(options[i]).remove();
$("[id*=LeftBox]").append(opt);
}
});
$("#right").bind("click", function () {
var options = $("[id*=LeftBox] option:selected");
for (var i = 0; i < options.length; i++) {
var opt = $(options[i]).clone();
$(options[i]).remove();
$("[id*=RightBox]").append(opt);
}
});
});
I've been at it for a while Now. I know I'm probably missing something obvious but its annoying me now.
Any Help would be appreciated thanks.
Your can simplify your existing scripts to
$("#left").click(function () {
$('#RightBox').append($('#LeftBox').children(':selected'));
});
$("#right").click(function () {
$('#LeftBox').append($('#RightBox').children(':selected'));
});
To transfer an option from one select to the other when an option is double clicked
$("#LeftBox").on('dblclick', 'option', function () {
$('#RightBox').append($(this));
})
$("#RightBox").on('dblclick', 'option', function () {
$('#LeftBox').append($(this));
})
Note also you have 2 select controls bound to the same property and will generate the same <select name="SelectedRoleID" ...> so this wont post back correctly. You should bind each to a different property (in which case, just use the ID's generated by the html helper). You are also using #Label("Available Roles") which is not creating a true label element since its not associated with the control. Use #LabelFor(m => m.SelectedRoleID).

How to trigger a function from a object

I want to get the console.log to output true or false on click of .icons div.
Here is some live code: codepen
I have a model called navigation
$scope.navigation = {
status: false;
}
And a big object that will toggle the ui
$scope.toggle = {
menu: function() {
$scope.navigation.status = !$scope.navigation.status;
alert($scope.navigation.status);
}
};
The trigger is an ng-click="open.menu()" :
<div class="icons " ng-repeat="(key, val) in squares" ng-style="perspective" ng-click="toggle.menu()">
Try:
toggle.menu()
instead of
open.menu()

How can I manipulate dynamically created elements wth jquery?

I am a jquery newbie and i am creating boxes with jquery and then "deleting" them. But I want to use the same code to delete the box in the scope of the created element and the scope of a already created element.
Html:
<button id="create">Cria</button>
<div id="main">
<div class="box">
<a class="del-btn" href="#">Delete</a>
</div>
</div>
JS:
var box = {
create: function() {
var box = $('<div class="box">');
var delBtn = $('<a class="del-btn" href="#">Delete</a>');
box.appendTo('#main');
delBtn.appendTo(box);
},
destroy: function(elem) {
elem.fadeOut();
}
}
function deleteBox () {
}
$(function() {
$('#create').click(function() {
box.create();
});
$('.del-btn').click(function() {
var elem = $(this).parent();
box.destroy(elem);
return false;
});
});
If I put the delete event inside the create click event, I just can delete the dynamically created element. If I put it outside, then I can just delete the element in the HTML. I know this is a simple question, but I can't figure out how to solve it. Thanks
You can use delegated-events approach:
$("#main").on("click", ".del-btn", function() {
var elem = $(this).parent();
box.destroy(elem);
return false;
});

Edit object via modal in AngularJS - use a temporary object?

Scenario: User clicks on item. Following code runs and opens a modal with a textbox that has the item's name populated.
$scope.edit = function (item) {
$scope.editingItem = { Name: item.Name };
};
My HTML within the modal:
<input type="text" ng-model="editingItem.Name"/>
This works fine, the modal shows (using ng-show) and the textbox is populated with the item's name.
Im using a new object to populate the textbox because I don't want the original object to change (via AngularJS auto data binding) until I press the save button.
Then this HTML:
Save
Leads to:
$scope.update = function (item) {
// What do I put in here to update the original item object that was passed
// into the edit function at the top of this question?!
};
My issue though is what to put into the update method? I want to update the original item (held in an array of items).
I would do this:
$scope.edit = function (item) {
$scope.editingItem = { Name: item.Name };
$scope.originalItem = item;//store the instance of the item to edit
};
$scope.update = function (item) {
$scope.originalItem.Name = item.Name;//copy the attributes you need to update
//clear temp items to remove any bindings from the input
$scope.originalItem = undefined;
$scope.editingItem = undefined;
};

Categories

Resources