问题
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
回答1:
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 :-)
回答2:
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/).
回答3:
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
回答4:
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
来源:https://stackoverflow.com/questions/16195200/creating-a-autocomplete-field-with-typeahead-in-the-durandal-framework