I'm trying to achieve a autocomplete input field with typeahead (twitter bootstrap). This autocomplete field should be in a modal but I cant seem to get it working! It also must be observable with knockout because when you select a value other fields should be updated to.
So I'm kinda looking to do this in a modal!
HTML
<div class="messageBox">
<div class="modal-header">
<h2>Adding a repairline</h2>
</div>
<div class="modal-body" style="width: 35em;">
<form data-bind="submit: ok">
<fieldset>
<!--<legend></legend> Deze niet toevoegen uitlijning is dan niet goed!-->
<div class="row-fluid">
<div class="span6">
<div>
<label>
Description:
<input type="text" id="testen" data-provide="typeahead" />
</label>
</div>
<div>
<label>
Code:
<input data-bind="value: Code" required />
</label>
</div>
<div>
<input class="btn btn-primary" type="submit" value="Add" />
</div>
</fieldset>
</form>
</div>
<div class="modal-footer">
<button class="btn" data-bind="click: closeModal">Cancel</button>
</div>
</div>
JS
define(function (require) {
var dataservice = require('services/dataservice'),
allCustomers = ko.observableArray([]),
repairlines = ko.observableArray([]);
function init() {
dataservice = new dataservice('api/data');
dataservice.getAllRows('AllCustomers').then(function (data) {
data.results.forEach(function (item) {
allCustomers.push(item);
});
}).fail(function () {
});
dataservice.getAllRows('EntireRepairLineLib').then(function (data) {
data.results.forEach(function (item) {
repairlines.push(item);
});
}).fail(function () {
});
$('.testen .typeahead').typeahead({
name: 'countries',
prefetch: 'http://twitter.github.io/typeahead.js/data/countries.json',
limit: 10
});
}
init();
AddRepairOrderLineModal = function (loggedInEmployee) {
//later ook customer en repairorder meegeven!
this.allCustomers = allCustomers;
this.choosenCustomerId = ko.observable(); //holds the Id of the chosen customer
this.Description = ko.observable();
this.Code = ko.observable();
this.Mat = ko.observable();
this.Location = ko.observable();
this.Rep = ko.observable();
this.Dum = ko.observable();
this.Dam = ko.observable();
this.Qty = ko.observable();
this.Hours = ko.observable();
this.Tariff = ko.observable();
this.Costs = ko.observable();
this.CreationDate = (new Date().getMonth() + 1) + "-" + new Date().getDate() + "-" + new Date().getFullYear();
this.IsAgreement = ko.observable(true);
this.IsAuthorized = ko.observable(true);
this.DoRepair = ko.observable(true);
this.selectedEmployee = loggedInEmployee;
/* $(".repairlinename").autocomplete({
source: repairlines
});*/
$(document).ready(function() {
alert('done');
$('#testen').append(' Leroy');
});
};
AddRepairOrderLineModal.prototype.ok = function () {
var jsonObj = [];
jsonObj.push({
Description: this.Description(), Code: this.Code(),
Mat: this.Mat(), Location: this.Location(),
Rep: this.Rep(), Dum: this.Dum(), Dam: this.Dam(),
CustomerId: this.choosenCustomerId(), Qty: this.Qty(), Hours: this.Hours(),
Tariff: this.Tariff(), Costs: this.Costs(), CreationDate: this.CreationDate,
IsAgreement: this.IsAgreement(), IsAuthorized: this.IsAuthorized(), DoRepair: this.DoRepair(),
});
this.modal.close(jsonObj);
};
AddRepairOrderLineModal.prototype.closeModal = function () {
return this.modal.close();
};
return AddRepairOrderLineModal;
});
/*define(['durandal/app','services/dataservice'], function(app,dataservice) {
AddRepairOrderLineModal = function (loggedInEmployee) {
//later ook customer en repairorder meegeven!
this.Description = ko.observable();
this.Code = ko.observable();
this.Mat = ko.observable();
this.Location = ko.observable();
this.Rep = ko.observable();
this.Dum = ko.observable();
this.Dam = ko.observable();
this.Customer = ko.observable();
this.Qty = ko.observable();
this.Hours = ko.observable();
this.Tariff = ko.observable();
this.Costs = ko.observable();
this.CreationDate = (new Date().getMonth() + 1) + "-" + new Date().getDate() + "-" + new Date().getFullYear();
this.IsAgreement = ko.observable(true);
this.IsAuthorized = ko.observable(true);
this.DoRepair = ko.observable(true);
this.selectedEmployee = loggedInEmployee;
};
AddRepairOrderLineModal.prototype.ok = function () {
var jsonObj = [];
jsonObj.push({
Description: this.Description(), Code: this.Code(),
Mat: this.Mat(), Location: this.Location(),
Rep: this.Rep(), Dum: this.Dum(), Dam: this.Dam(),
Customer: this.Customer(), Qty: this.Qty(), Hours: this.Hours(),
Tariff: this.Tariff(), Costs: this.Costs(), CreationDate: this.CreationDate,
IsAgreement: this.IsAgreement(), IsAuthorized: this.IsAuthorized(), DoRepair: this.DoRepair()
});
this.modal.close(jsonObj);
};
AddRepairOrderLineModal.prototype.closeModal = function() {
return this.modal.close();
};
return AddRepairOrderLineModal;
/*
http://stackoverflow.com/questions/7537002/autocomplete-combobox-with-knockout-js-template-jquery
http://jsfiddle.net/rniemeyer/PPsRC/
*//*
});
*/
I hope someone can help me on how to do this! The source is repairlines, these are all filled in correctly
You would need to bind the typeahead input to an array exposed on your view model. I don't think you are doing either at the moment.
To do the bindings you should use knockout-bootstrap bindings found here:
http://billpull.github.io/knockout-bootstrap/
Once you have included the above knockout-bootstrap bindings you can do this in your view:
<input type="text" data-bind="typeahead: repairlines" />
Then make sure you are adding repairlines to your VM instance. Adding it to your this reference should do the trick.
// this will add the repairlines observable array to your VM instance
this.repairlines = repairlines;
Hope this helps and good luck :-)
I would comment, but can't so here's my note. In your example you have a same origin policy (SOP) issue. So the data from the twitter page isn't getting pulled in. That pretty much kills the process so you don't end up with anything.
I found that if I include the appropriate styles (such as one for tt-dropdown-menu):
<span class="tt-dropdown-menu" style="position: absolute; top: 100%; left: 0px; z-index: 100; display: block; right: auto;"></span>
and have a functioning dataset it works fine. Here's a fiddle of my attempt http://jsfiddle.net/rUdXt/ and here is a great page that helped me (especially with the styles http://twitter.github.io/typeahead.js/examples/).
I have fixed this problem by adding some extra code to the library: https://github.com/billpull/knockout-bootstrap
This is the knockout-bootstrap.js, where ko.bindingHandlers.typeahead is rewritten so it accept updates, minLength en items. Just load this script in.
//UUID
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
// Outer HTML
(function($){
$.fn.outerHtml = function() {
if (this.length == 0) return false;
var elem = this[0], name = elem.tagName.toLowerCase();
if (elem.outerHTML) return elem.outerHTML;
var attrs = $.map(elem.attributes, function(i) { return i.name+'="'+i.value+'"'; });
return "<"+name+(attrs.length > 0 ? " "+attrs.join(" ") : "")+">"+elem.innerHTML+"</"+name+">";
};
})(jQuery);
// Bind twitter typeahead
ko.bindingHandlers.typeahead = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var allBindings = allBindingsAccessor();
var typeaheadArr = ko.utils.unwrapObservable(valueAccessor());
$element.attr("autocomplete", "off")
.typeahead({
'source': typeaheadArr,
'minLength': allBindings.minLength,
'items': allBindings.items,
'updater': allBindings.updater
});
}
};
/*
ko.bindingHandlers.typeahead = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var t = valueAccessor();
var typeaheadArr = ko.utils.unwrapObservable(valueAccessor());
var k = allBindingsAccessor().v1;
$element.attr("autocomplete", "off")
.typeahead({
'source' : typeaheadArr
});
}
};
*/
// Bind Twitter Progress
ko.bindingHandlers.progress = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var bar = $('<div/>', {
'class':'bar',
'data-bind':'style: { width:' + valueAccessor() + ' }'
});
$element.attr('id', guid())
.addClass('progress progress-info')
.append(bar);
ko.applyBindingsToDescendants(viewModel, $element[0]);
}
}
// Bind Twitter Alert
ko.bindingHandlers.alert = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $element = $(element);
var alertInfo = ko.utils.unwrapObservable(valueAccessor());
var dismissBtn = $('<button/>', {
'type':'button',
'class':'close',
'data-dismiss':'alert'
}).html('×');
var alertMessage = $('<p/>').html(alertInfo.message);
$element.addClass('alert alert-'+alertInfo.priority)
.append(dismissBtn)
.append(alertMessage);
}
};
// Bind Twitter Tooltip
ko.bindingHandlers.tooltip = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// read tooltip options
var tooltipBindingValues = ko.utils.unwrapObservable(valueAccessor());
// set tooltip title
var tooltipTitle = tooltipBindingValues.title;
// set tooltip placement
var tooltipPlacement = tooltipBindingValues.placement;
// set tooltip trigger
var tooltipTrigger = tooltipBindingValues.trigger;
var options = {
title: tooltipTitle
};
ko.utils.extend(options, ko.bindingHandlers.tooltip.options);
if (tooltipPlacement) {
options.placement = tooltipPlacement;
}
if (tooltipTrigger) {
options.trigger = tooltipTrigger;
}
$(element).tooltip(options);
},
options: {
placement: "top",
trigger: "hover"
}
};
// Bind Twitter Popover
ko.bindingHandlers.popover = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// read popover options
var popoverBindingValues = ko.utils.unwrapObservable(valueAccessor());
// set popover title
var popoverTitle = popoverBindingValues.title;
// set popover template id
var tmplId = popoverBindingValues.template;
// set popover trigger
var trigger = 'click';
if (popoverBindingValues.trigger) {
trigger = popoverBindingValues.trigger;
}
// update triggers
if (trigger === 'hover') {
trigger = 'mouseenter mouseleave';
} else if (trigger === 'focus') {
trigger = 'focus blur';
}
// set popover placement
var placement = popoverBindingValues.placement;
// get template html
var tmplHtml = $('#' + tmplId).html();
// create unique identifier to bind to
var uuid = guid();
var domId = "ko-bs-popover-" + uuid;
// create correct binding context
var childBindingContext = bindingContext.createChildContext(viewModel);
// create DOM object to use for popover content
var tmplDom = $('<div/>', {
"class" : "ko-popover",
"id" : domId
}).html(tmplHtml);
// set content options
options = {
content: $(tmplDom[0]).outerHtml(),
title: popoverTitle
};
if (placement) {
options.placement = placement;
}
// Need to copy this, otherwise all the popups end up with the value of the last item
var popoverOptions = $.extend({}, ko.bindingHandlers.popover.options, options);
// bind popover to element click
$(element).bind(trigger, function () {
var popoverAction = 'show';
var popoverTriggerEl = $(this);
// popovers that hover should be toggled on hover
// not stay there on mouseout
if (trigger !== 'click') {
popoverAction = 'toggle';
}
// show/toggle popover
popoverTriggerEl.popover(popoverOptions).popover(popoverAction);
// hide other popovers and bind knockout to the popover elements
var popoverInnerEl = $('#' + domId);
$('.ko-popover').not(popoverInnerEl).parents('.popover').remove();
// if the popover is visible bind the view model to our dom ID
if($('#' + domId).is(':visible')){
ko.applyBindingsToDescendants(childBindingContext, $('#' + domId)[0]);
}
// bind close button to remove popover
$(document).on('click', '[data-dismiss="popover"]', function (e) {
popoverTriggerEl.popover('hide');
});
});
// Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
return { controlsDescendantBindings: true };
},
options: {
placement: "right",
title: "",
html: true,
content: "",
trigger: "manual"
}
};
In your HTML do this.
<input type="text" data-bind="typeahead: repairlines(), minLength: 2, updater: updateViewAfterSelection, value: Description" required/>
This is my viewmodel:
define(function (require) {
var dataservice = require('services/dataservice'),
allCustomers = ko.observableArray([]),
repairlinesRaw = ko.observableArray([]),
Description = ko.observable(),
InternalCode = ko.observable(),
Mat = ko.observable(),
Location = ko.observable(),
Rep = ko.observable(),
Dum = ko.observable(),
Dam = ko.observable(),
Qty = ko.observable(0).extend({ numeric: 2 }),
Hours = ko.observable(0).extend({ numeric: 2 }),
Tariff = ko.observable(0).extend({ numeric: 2 }),
Costs = ko.observable(0).extend({ numeric: 2 });
function init() {
dataservice = new dataservice('api/data');
dataservice.getAllRows('AllCustomers').then(function (data) {
data.results.forEach(function (item) {
allCustomers.push(item);
});
}).fail(function () {
});
dataservice.getAllRows('EntireRepairLineLib').then(function (data) {
data.results.forEach(function (item) {
repairlinesRaw.push(item);
});
}).fail(function () {
});
}
init();
AddRepairOrderLineModal = function (loggedInEmployee) {
//later ook customer en repairorder meegeven!
this.allCustomers = allCustomers;
this.choosenCustomerId = ko.observable(); //holds the Id of the chosen customer
this.Description = Description;
this.InternalCode = InternalCode;
this.Mat = Mat;
this.Location = Location;
this.Rep = Rep;
this.Dam = Dam;
this.Qty = Qty;
this.Hours = Hours;
this.Tariff = Tariff;
this.Costs = Costs;
//this.CreationDate = (new Date().getMonth() + 1) + "-" + new Date().getDate() + "-" + new Date().getFullYear();
this.repairlines = function () {
var repairlinesName = [];
map = {};
var data = repairlinesRaw();
$.each(data, function (i, repairline) {
map[repairline.Description()] = repairline;
repairlinesName.push(repairline.Description());
});
return repairlinesName;
};
this.IsAgreement = ko.observable(true);
this.IsAuthorized = ko.observable(true);
this.DoRepair = ko.observable(true);
this.selectedEmployee = loggedInEmployee;
};
AddRepairOrderLineModal.prototype.updateViewAfterSelection = function(item) {
//map can be found in the repairlines function
Description(map[item].Description());
InternalCode(map[item].InternalCode());
Mat(map[item].MaterialCode());
Location(map[item].LocationCode());
Rep(map[item].RepairCode());
Dam(map[item].DamageCode());
Qty(map[item].Quantity());
Hours(map[item].Hours());
Costs(map[item].Costs());
return item;
};
AddRepairOrderLineModal.prototype.ok = function () {
var jsonObj = [];
jsonObj.push({
Description: this.Description(), InternalCode: this.InternalCode(),
Mat: this.Mat(), Location: this.Location(),
Rep: this.Rep(), Dam: this.Dam(),
CustomerId: this.choosenCustomerId(), Qty: this.Qty(), Hours: this.Hours(),
Tariff: this.Tariff(), Costs: this.Costs(),
IsAgreement: this.IsAgreement(), IsAuthorized: this.IsAuthorized(), DoRepair: this.DoRepair(),
});
//empty all fields after JSON
//Otherwise the values would still be there when a new modal is opened again.
Description(null);
InternalCode(null);
Mat(null);
Location(null);
Rep(null);
Dum(null);
Dam(null);
Qty(0);
Hours(0);
Tariff(0);
Costs(0);
this.modal.close(jsonObj);
};
AddRepairOrderLineModal.prototype.closeModal = function () {
return this.modal.close();
};
return AddRepairOrderLineModal;
});
Hope people will understand this ;), hope it helps, alot of credits go to Bill Pull ;) and Alex Preston
Cannot understand this as it is TOO messy. That is, the typeahead bit is mix in with all your other stuff.
What about a simple example that has only typeahead stuff and maybe is composed to a view/viewmodel like a widget so it can be reused.
Then we could have a chance to understand it