asp.net-mvc - tutorial - viewmodel asp net mvc 4
¿Cómo usar knockout.js con ASP.NET MVC ViewModels? (3)
Generosidad
Ha pasado un tiempo y todavía tengo un par de preguntas pendientes. Espero al agregar una recompensa, tal vez estas preguntas serán respondidas.
- ¿Cómo se usan los helpers html con knockout.js?
¿Por qué estaba listo el documento para hacerlo funcionar? (Ver la primera edición para más información)
¿Cómo hago algo como esto si estoy usando el mapeo directo con mis modelos de vista? Como no tengo una función debido a la asignación.
function AppViewModel() { // ... leave firstName, lastName, and fullName unchanged here ... this.capitalizeLastName = function() { var currentVal = this.lastName(); // Read the current value this.lastName(currentVal.toUpperCase()); // Write back a modified value };
Deseo usar complementos, por ejemplo, quiero poder deshacer observables, como si un usuario cancelara una solicitud, quiero volver al último valor. De mi investigación, esto parece ser logrado por personas que hacen complementos como editables
¿Cómo uso algo así si estoy usando mapeo? Realmente no quiero ir a un método donde tengo en mi vista mapeo manual si asignara cada campo MVC viewMode a un campo de modelo KO ya que quiero el menor javascript en línea posible y eso parece duplicar el trabajo y eso es por qué me gusta ese mapeo.
Me preocupa que para facilitar este trabajo (mediante el mapeo) perdería mucho poder KO, pero por otro lado estoy preocupado de que el mapeo manual sea mucho trabajo y haga que mis vistas contengan demasiada información y podría ser más difícil de mantener en el futuro (por ejemplo, si elimino una propiedad en el modelo MVC, tengo que moverla también en el modelo de vista KO)
Estoy usando asp.net mvc 3 y estoy buscando un nocaut, ya que se ve muy bien, pero me está costando trabajo averiguar cómo funciona con asp.net mvc, especialmente ver modelos.
Para mí, ahora hago algo como esto
public class CourseVM
{
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(40, ErrorMessage = "Course name cannot be this long.")]
public string CourseName{ get; set; }
public List<StudentVm> StudentViewModels { get; set; }
}
Tendría una Vm que tiene algunas propiedades básicas como CourseName y tendrá una validación simple en la parte superior. El modelo de Vm también puede contener otros modelos de vista, si es necesario.
Luego pasaba esta Vm a la Vista si utilizaba helpers html para ayudarme a mostrarla al usuario.
@Html.TextBoxFor(x => x.CourseName)
Es posible que tenga algunos bucles foreach o algo para obtener los datos de la colección de Modelos de vista del alumno.
Luego, cuando enviaba el formulario, utilizaba jquery y serialize array
y lo enviaba a un método de acción del controlador que lo vincularía de nuevo al viewmodel.
Con knockout.js todo es diferente, ya que ahora tiene viewmodels y de todos los ejemplos que he visto, no usan helpers html.
¿Cómo utilizas estas 2 características de MVC con knockout.js?
Encontré este video y lo resumí brevemente (los últimos minutos del video @ 18:48) se usa para ver modelos usando básicamente un script en línea que tiene el modelo de vista knockout.js al que se le asignan los valores en ViewModel.
¿Es esta la única manera de hacerlo? ¿Qué tal si en mi ejemplo tengo una colección de modelos de vista? ¿Tengo que tener un bucle Foreach o algo para extraer todos los valores y asignarlo a knockout?
En cuanto a los helpers html, el video no dice nada sobre ellos.
Estas son las 2 áreas que me confunden, ya que no muchas personas parecen hablar de ello y me deja confundido de cómo los valores iniciales y todo lo que está llegando a la vista cuando el ejemplo es solo un ejemplo de valor codificado.
EditarEstoy intentando lo que Darin Dimitrov ha sugerido y parece funcionar (aunque tuve que hacer algunos cambios en su código). No estoy seguro de por qué tenía que usar el documento listo pero de alguna manera todo no estaba listo sin él.
@model MvcApplication1.Models.Test
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = @Html.Raw(Json.Encode(Model));
// Activates knockout.js
ko.applyBindings(model);
});
</script>
</head>
<body>
<div>
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
@Model.FirstName , @Model.LastName
</div>
</body>
</html>
Tenía que envolverlo en un documento jquery listo para hacerlo funcionar.
También recibo esta advertencia. No estoy seguro de qué se trata todo esto.
Warning 1 Conditional compilation is turned off -> @Html.Raw
Así que tengo un punto de partida, supongo que al menos se actualizará cuando haya jugado un poco más y cómo funciona esto.
Estoy tratando de ir a través de los tutoriales interactivos, pero en su lugar uso un ViewModel.
No estoy seguro de cómo abordar estas partes aún
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
}
o
function AppViewModel() {
// ... leave firstName, lastName, and fullName unchanged here ...
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
Editar 2
Pude descubrir el primer problema. No hay ninguna pista sobre el segundo problema. Sin embargo, sin embargo. ¿Alguien tiene alguna idea?
@model MvcApplication1.Models.Test
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="../../Scripts/knockout-2.1.0.js" type="text/javascript"></script>
<script src="../../Scripts/knockout.mapping-latest.js" type="text/javascript"></script>
<script type="text/javascript">
$(function()
{
var model = @Html.Raw(Json.Encode(Model));
var viewModel = ko.mapping.fromJS(model);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<div>
@*grab values from the view model directly*@
<p>First name: <strong data-bind="text: FirstName"></strong></p>
<p>Last name: <strong data-bind="text: LastName"></strong></p>
@*grab values from my second view model that I made*@
<p>SomeOtherValue <strong data-bind="text: Test2.SomeOtherValue"></strong></p>
<p>Another <strong data-bind="text: Test2.Another"></strong></p>
@*allow changes to all the values that should be then sync the above values.*@
<p>First name: <input data-bind="value: FirstName" /></p>
<p>Last name: <input data-bind="value: LastName" /></p>
<p>SomeOtherValue <input data-bind="value: Test2.SomeOtherValue" /></p>
<p>Another <input data-bind="value: Test2.Another" /></p>
@* seeing if I can do it with p tags and see if they all update.*@
<p data-bind="foreach: Test3">
<strong data-bind="text: Test3Value"></strong>
</p>
@*took my 3rd view model that is in a collection and output all values as a textbox*@
<table>
<thead><tr>
<th>Test3</th>
</tr></thead>
<tbody data-bind="foreach: Test3">
<tr>
<td>
<strong data-bind="text: Test3Value"></strong>
<input type="text" data-bind="value: Test3Value"/>
</td>
</tr>
</tbody>
</table>
Controlador
public ActionResult Index()
{
Test2 test2 = new Test2
{
Another = "test",
SomeOtherValue = "test2"
};
Test vm = new Test
{
FirstName = "Bob",
LastName = "N/A",
Test2 = test2,
};
for (int i = 0; i < 10; i++)
{
Test3 test3 = new Test3
{
Test3Value = i.ToString()
};
vm.Test3.Add(test3);
}
return View(vm);
}
Creo que he resumido todas sus preguntas, si me perdí algo, hágamelo saber ( si pudiera resumir todas sus preguntas en un solo lugar sería bueno =))
Nota. Compatibilidad con el complemento ko.editable
agregado
Download el código completo
¿Cómo se usan los helpers html con knockout.js?
Esto es facil:
@Html.TextBoxFor(model => model.CourseId, new { data_bind = "value: CourseId" })
Dónde:
-
value: CourseId
indica que está vinculando la propiedad devalue
del control deinput
con la propiedadCourseId
de su modelo y su modelo de secuencia de comandos
El resultado es:
<input data-bind="value: CourseId" data-val="true" data-val-number="The field CourseId must be a number." data-val-required="The CourseId field is required." id="CourseId" name="CourseId" type="text" value="12" />
¿Por qué estaba listo el documento para hacerlo funcionar? (Ver la primera edición para más información)
Aún no entiendo por qué necesita usar el evento ready
para serializar el modelo, pero parece que simplemente es obligatorio (sin embargo, no se preocupe)
¿Cómo hago algo como esto si estoy usando el mapeo directo con mis modelos de vista? Como no tengo una función debido a la asignación.
Si entiendo correctamente, debe agregar un nuevo método al modelo KO, así que es fácil fusionar modelos
Para más información, en la sección -Mapeo de diferentes fuentes-
function viewModel() {
this.addStudent = function () {
alert("de");
};
};
$(function () {
var jsonModel = ''@Html.Raw(JsonConvert.SerializeObject(this.Model))'';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new viewModel();
var g = ko.mapping.fromJS(myViewModel, mvcModel);
ko.applyBindings(g);
});
Acerca de la advertencia que estabas recibiendo
Advertencia 1 La compilación condicional está desactivada -> @ Html.Raw
Necesitas usar citas
Compatibilidad con el complemento ko.editable
Pensé que iba a ser más complejo, pero resultó que la integración es realmente fácil, para que su modelo sea editable solo agregue la siguiente línea: (recuerde que en este caso estoy usando un modelo mixto, desde el servidor y agregar extensión en el cliente y editable simplemente funciona ... es genial):
ko.editable(g);
ko.applyBindings(g);
Desde aquí solo necesitas jugar con tus enlaces utilizando las extensiones agregadas por el complemento, por ejemplo, tengo un botón para comenzar a editar mis campos de esta manera y en este botón empiezo el proceso de edición:
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
Luego tengo botones de confirmación y cancelación con el siguiente código:
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
Y finalmente, tengo un campo para indicar si los campos están en modo de edición o no, esto es solo para enlazar la propiedad enable.
this.isInEditMode = ko.observable(false);
Acerca de su pregunta matriz
Es posible que tenga algunos bucles foreach o algo para obtener los datos de la colección de Modelos de vista del alumno.
Luego, cuando enviaba el formulario, utilizaba jquery y serialize array y lo enviaba a un método de acción del controlador que lo vincularía de nuevo al viewmodel.
Puede hacer lo mismo con KO, en el siguiente ejemplo, crearé el siguiente resultado:
Básicamente aquí, tiene dos listas, creadas usando Helpers
y vinculadas con KO, tienen un evento dblClick
que cuando se dispara, elimina el elemento seleccionado de la lista actual y lo agrega a la otra lista, cuando publica en el Controller
, el el contenido de cada lista se envía como datos JSON y se vuelve a adjuntar al modelo de servidor
Nuggets:
scripts externos.
Código de controlador
[HttpGet]
public ActionResult Index()
{
var m = new CourseVM { CourseId = 12, CourseName = ".Net" };
m.StudentViewModels.Add(new StudentVm { ID = 545, Name = "Name from server", Lastname = "last name from server" });
return View(m);
}
[HttpPost]
public ActionResult Index(CourseVM model)
{
if (!string.IsNullOrWhiteSpace(model.StudentsSerialized))
{
model.StudentViewModels = JsonConvert.DeserializeObject<List<StudentVm>>(model.StudentsSerialized);
model.StudentsSerialized = string.Empty;
}
if (!string.IsNullOrWhiteSpace(model.SelectedStudentsSerialized))
{
model.SelectedStudents = JsonConvert.DeserializeObject<List<StudentVm>>(model.SelectedStudentsSerialized);
model.SelectedStudentsSerialized = string.Empty;
}
return View(model);
}
Modelo
public class CourseVM
{
public CourseVM()
{
this.StudentViewModels = new List<StudentVm>();
this.SelectedStudents = new List<StudentVm>();
}
public int CourseId { get; set; }
[Required(ErrorMessage = "Course name is required")]
[StringLength(100, ErrorMessage = "Course name cannot be this long.")]
public string CourseName { get; set; }
public List<StudentVm> StudentViewModels { get; set; }
public List<StudentVm> SelectedStudents { get; set; }
public string StudentsSerialized { get; set; }
public string SelectedStudentsSerialized { get; set; }
}
public class StudentVm
{
public int ID { get; set; }
public string Name { get; set; }
public string Lastname { get; set; }
}
Página CSHTML
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
<legend>CourseVM</legend>
<div>
<div class="editor-label">
@Html.LabelFor(model => model.CourseId)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.CourseId, new { data_bind = "enable: isInEditMode, value: CourseId" })
@Html.ValidationMessageFor(model => model.CourseId)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.CourseName)
</div>
<div class="editor-field">
@Html.TextBoxFor(model => model.CourseName, new { data_bind = "enable: isInEditMode, value: CourseName" })
@Html.ValidationMessageFor(model => model.CourseName)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.StudentViewModels);
</div>
<div class="editor-field">
@Html.ListBoxFor(
model => model.StudentViewModels,
new SelectList(this.Model.StudentViewModels, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: StudentViewModels, optionsText: ''Name'', value: leftStudentSelected, event: { dblclick: moveFromLeftToRight }"
}
)
@Html.ListBoxFor(
model => model.SelectedStudents,
new SelectList(this.Model.SelectedStudents, "ID", "Name"),
new
{
style = "width: 37%;",
data_bind = "enable: isInEditMode, options: SelectedStudents, optionsText: ''Name'', value: rightStudentSelected, event: { dblclick: moveFromRightToLeft }"
}
)
</div>
@Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
@Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
@Html.HiddenFor(model => model.StudentsSerialized, new { data_bind = "value: StudentsSerialized" })
@Html.HiddenFor(model => model.SelectedStudentsSerialized, new { data_bind = "value: SelectedStudentsSerialized" })
</div>
<p>
<input type="submit" value="Save" data-bind="enable: !isInEditMode()" />
<button data-bind="enable: !isInEditMode(), click: editMode">Edit mode</button><br />
<div>
<button data-bind="enable: isInEditMode, click: addStudent">Add Student</button>
<button data-bind="enable: hasChanges, click: executeCommit">Commit</button>
<button data-bind="enable: isInEditMode, click: executeRollback">Cancel</button>
</div>
</p>
</fieldset>
}
Guiones
<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout-2.1.0.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ko.editables.js")" type="text/javascript"></script>
<script type="text/javascript">
var g = null;
function ViewModel() {
this.addStudent = function () {
this.StudentViewModels.push(new Student(25, "my name" + new Date(), "my last name"));
this.serializeLists();
};
this.serializeLists = function () {
this.StudentsSerialized(ko.toJSON(this.StudentViewModels));
this.SelectedStudentsSerialized(ko.toJSON(this.SelectedStudents));
};
this.leftStudentSelected = ko.observable();
this.rightStudentSelected = ko.observable();
this.moveFromLeftToRight = function () {
this.SelectedStudents.push(this.leftStudentSelected());
this.StudentViewModels.remove(this.leftStudentSelected());
this.serializeLists();
};
this.moveFromRightToLeft = function () {
this.StudentViewModels.push(this.rightStudentSelected());
this.SelectedStudents.remove(this.rightStudentSelected());
this.serializeLists();
};
this.isInEditMode = ko.observable(false);
this.executeCommit = function () {
this.commit();
this.isInEditMode(false);
};
this.executeRollback = function () {
if (this.hasChanges()) {
if (confirm("Are you sure you want to discard the changes?")) {
this.rollback();
this.isInEditMode(false);
}
}
else {
this.rollback();
this.isInEditMode(false);
}
};
this.editMode = function () {
this.isInEditMode(!this.isInEditMode());
this.beginEdit();
};
}
function Student(id, name, lastName) {
this.ID = id;
this.Name = name;
this.LastName = lastName;
}
$(function () {
var jsonModel = ''@Html.Raw(JsonConvert.SerializeObject(this.Model))'';
var mvcModel = ko.mapping.fromJSON(jsonModel);
var myViewModel = new ViewModel();
g = ko.mapping.fromJS(myViewModel, mvcModel);
g.StudentsSerialized(ko.toJSON(g.StudentViewModels));
g.SelectedStudentsSerialized(ko.toJSON(g.SelectedStudents));
ko.editable(g);
ko.applyBindings(g);
});
</script>
Nota: acabo de agregar estas líneas:
@Html.HiddenFor(model => model.CourseId, new { data_bind="value: CourseId" })
@Html.HiddenFor(model => model.CourseName, new { data_bind="value: CourseName" })
Porque cuando envío el formulario, mis campos están deshabilitados, por lo que los valores no se transmitieron al servidor, es por eso que agregué un par de campos ocultos para hacer el truco
Para lograr las propiedades calculadas adicionales después de la asignación del servidor, deberá mejorar aún más sus modelos de vista en el lado del cliente.
Por ejemplo:
var viewModel = ko.mapping.fromJS(model);
viewModel.capitalizedName = ko.computed(function() {...}, viewModel);
Por lo tanto, cada vez que realice un mapeo desde JSON sin procesar, deberá volver a aplicar las propiedades calculadas.
Además, el complemento de mapeo brinda la capacidad de actualizar incrementalmente un modelo de vista en lugar de recrearlo cada vez que retrocede (use un parámetro adicional en fromJS
):
// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);
Y eso ejecuta una actualización incremental de datos en su modelo de propiedades justas que están mapeadas. Puede leer más sobre eso en la documentación de mapeo
Usted mencionó en los comentarios sobre la respuesta de Darin el paquete FluentJSON . Soy el autor de eso, pero su caso de uso es más específico que ko.mapping. Por lo general, solo lo usaría si sus modelos de vista son de una sola manera (es decir, servidor -> cliente) y luego los datos se publican en un formato diferente (o no se publican). O si su modelo de vista de JavaScript necesita estar en un formato sustancialmente diferente de su modelo de servidor.
Puede serializar su modelo de vista ASP.NET MVC en una variable de JavaScript:
@model CourseVM
<script type="text/javascript">
var model = @Html.Raw(Json.Encode(Model));
// go ahead and use the model javascript variable to bind with ko
</script>
Hay muchos ejemplos en la documentación de nocaut que podría pasar.