5.在MVC中使用泛型仓储模式和工作单元来进行增删查改

2023-07-29,,

原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pattern-and-uni/

或者:http://www.codeproject.com/Articles/814768/CRUD-Operations-Using-the-Generic-Repository-Patte

系列目录:

Relationship in Entity Framework Using Code First Approach With Fluent API【【使用EF Code-First方式和Fluent API来探讨EF中的关系】】
Code First Migrations with Entity Framework【使用EF 做数据库迁移】
CRUD Operations Using Entity Framework 5.0 Code First Approach in MVC【在MVC中使用EF 5.0做增删查改】
CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式,来做增删查改】
CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC【在MVC中使用泛型仓储模式和工作单元来做增删查改】
CRUD Operations Using the Generic Repository Pattern and Dependency Injection in MVC【在MVC中使用泛型仓储模式和依赖注入,来做增删查改】

源代码下载:https://github.com/caofangsheng93/GenericRepositoryCURD

这篇文章,我将会介绍在ASP.NET MVC应用程序中使用泛型仓储模式和工作单元。我将开发一个程序,对Book实体进行增删查改,为了保证这篇文章简单,便于大家理解泛型仓储模式和工作单元,在这篇文章中,我将只会使用一个Book实体。

在上篇文章中“4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式进行增删查改】“讲到了仓储模式,里面我们为Book实体,创建了一个仓储类,但是这个仓储仅仅是只能为一个实体服务的。试想一下,如果是真正的企业级开发,我们会有很多实体,难道,我们要为每一个实体都创建一个仓储类么???显然是不现实的。对于这个问题,我们需要创建一个可以为所有实体公用的仓储,所以这里引入泛型仓储。这样就避免了重复编码。

下面的图中,讲到了两个开发者,讨论一个问题:"是否需要创建一个新的零件或者是利用已经存在的零件?"  ------如果你选择第一种,那么就是这篇文章讲到的,"4.CRUD Operations Using the Repository Pattern in MVC【在MVC中使用仓储模式进行增删查改】"

但我在这里选择第二种,也就是这篇文章,我将要讲到的。--使用泛型仓储模式来重用代码,减少冗余代码。

既然说到仓储,那么什么是仓储模式呢?

仓储模式旨在数据访问层和业务逻辑层之间创建一个抽象层。仓储模式是数据访问模式,旨在达到数据访问的更松散的耦合性。我们在单独的类,或者类库中创建数据访问的逻辑,这就是仓储。仓储的职责就是和业务逻辑层进行通信。

在这篇文章中,我将会为所有的实体设计一个公共的泛型仓储,另外还有一个工作单元类。这个工作单元类,为每个实体创建仓储的实例,然后仓储实例用来做增删查改操作。我在控制器中创建工作单元类的实例,然后依据具体的实体,创建仓储的实例,然后就可以使用仓储中的方法进行每个操作了。

下面的图表显示了仓储和EF数据上下文之间的关系。在图中,MVC控制器直接通过工作单元和仓储进行交互,而不是直接和EF进行交互。

讲到这里,大家可能就会有疑问了,为什么要使用工作单元呢???

工作单元就像它的名字一样,做某件事情。在这篇文章中,工作单元主要做的是:我们创建工作单元的实例,然后工作单元为我们初始化EF数据上下文,然后每个仓储的实例都使用同一个数据上下文实例进行数据库操作。因此工作单元就是,用来确保所有的仓储实例都使用同一个数据上下文实例。

好了理论到此为止,讲的差不多了。现在我们开始进入正题:

先看看项目的结构:

这三个项目就不用做过多介绍了吧,前面的文章已经说了很多次了...

EF.Entity类库中,添加BaseEntity实体:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Entity
{
public abstract class BaseEntity
{
/// <summary>
/// ID编号
/// </summary>
public int ID { get; set; } /// <summary>
/// 添加时间
/// </summary>
public DateTime AddedDate { get; set; } /// <summary>
/// 修改时间
/// </summary>
public DateTime ModifiedDate { get; set; } /// <summary>
/// IP地址
/// </summary>
public string IP { get; set; } }
}

Book实体:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Entity
{
public class Book:BaseEntity
{
/// <summary>
/// 书名
/// </summary>
public string Title { get; set; } /// <summary>
/// 作者
/// </summary>
public string Author { get; set; } /// <summary>
/// ISBN编号
/// </summary>
public string ISBN { get; set; } /// <summary>
/// 出版时间
/// </summary>
public DateTime PublishedDate { get; set; }
}
}

Entity.Data类库中BookMap类:

using EF.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Data
{
public class BookMap:EntityTypeConfiguration<Book>
{
public BookMap()
{
//配置主键
this.HasKey(s => s.ID); //配置字段
this.Property(s => s.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(s => s.Author).HasColumnType("nvarchar").HasMaxLength().IsRequired();
this.Property(s => s.AddedDate).IsRequired();
this.Property(s => s.IP).IsOptional();
this.Property(s => s.ISBN).HasColumnType("nvarchar").HasMaxLength().IsRequired();
this.Property(s => s.ModifiedDate).IsOptional();
this.Property(s => s.PublishedDate).IsRequired();
this.Property(s => s.Title).HasColumnType("nvarchar").HasMaxLength().IsRequired(); //配置表名
this.ToTable("Books");
}
}
}

EF数据上下文类:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace EF.Data
{
public class EFDbContext:DbContext
{
public EFDbContext()
: base("name=DbConnectionString")
{ } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
//base.OnModelCreating(modelBuilder);
}
}
}

然后启用数据库迁移,自动生成数据库,这里的步骤就省略了,因为前面的文章已经说了。

接着,在EF.Data项目中创建泛型仓储类,里面提供了增删查改的方法,这个泛型的仓储类中,有一个带DbContext参数的构造函数,所以当我们实例化仓储的时候,传递一个数据上下文对象给仓储,所有的实体就可以使用同一个数据上下文对象了。我们使用了数据上下文的SaveChange方法,但是你同样可以使用工作单元类的Save方法,因为这两者使用的是同一个数据上下文对象,下面是泛型的仓储类代码:【为了使文章更容易理解,这里我不创建泛型的仓储接口】。

 using EF.Entity;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Data
{
/// <summary>
/// 泛型仓储类
/// </summary>
/// <typeparam name="T"></typeparam>
public class Repository<T> where T:BaseEntity
{
/// <summary>
/// 数据上下文变量
/// </summary>
private readonly EFDbContext db;
string errorMessage = string.Empty; #region 封装属性
/// <summary>
/// 实体访问字段
/// </summary>
private IDbSet<T> entities; /// <summary>
/// 封装属性
/// </summary>
public IDbSet<T> Entities
{
get
{
if (entities == null)
{
return entities = db.Set<T>();
}
else
{
return entities;
}
}
}
#endregion #region 构造函数
public Repository(EFDbContext context)
{
this.db = context;
}
#endregion #region 泛型方法--根据Id查找实体
/// <summary>
/// 泛型方法--根据Id查找实体
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public T GetById(int id)
{
return this.entities.Find(id);
}
#endregion #region Insert
public void Insert(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
else
{
this.Entities.Add(entity);//把实体的状态设置为Added
this.db.SaveChanges();//保存到数据库
}
}
catch (DbEntityValidationException dbEx)//DbEntityValidationException
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var item in validationErrors.ValidationErrors)
{
errorMessage += string.Format("Property:{0} Error:{1}", item.PropertyName, item.ErrorMessage) + Environment.NewLine;
}
}
throw new Exception(errorMessage, dbEx);
}
}
#endregion #region Update
public void Update(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
else
{
this.db.SaveChanges();//保存到数据库
}
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var error in validationErrors.ValidationErrors)
{
errorMessage += string.Format("Property:{0} Error:{1}", error.PropertyName, error.ErrorMessage) + Environment.NewLine;
}
}
//抛出捕获的异常
throw new Exception(errorMessage, dbEx);
}
}
#endregion #region Delete
public void Delete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentException("entity");
}
else
{
this.db.Entry(entity).State = EntityState.Deleted;
this.db.SaveChanges();
}
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var error in validationErrors.ValidationErrors)
{
errorMessage += string.Format("Property:{0} Error:{1}", error.PropertyName, error.ErrorMessage) + Environment.NewLine;
}
} throw new Exception(errorMessage, dbEx);
}
}
#endregion #region Table
public virtual IQueryable<T> Table
{
get
{
return this.Entities;
}
}
#endregion }
}

下面创建工作单元类,UnitOfWork,这个工作单元类继承自IDisposable接口,所以它的实例将会在每个控制器中被 释放掉。工作单元类初始化了程序的上下文,工作单元类的核心就是Repository<T>() 类型的泛型方法,这个方法为继承自BaseEntity的每个实体返回了仓储实例。下面是工作单元类的代码:

using EF.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace EF.Data
{
public class UnitOfWork : IDisposable
{
private readonly EFDbContext db;
private bool disposed;
private Dictionary<string, object> repositories; public UnitOfWork(EFDbContext context)
{
this.db = context; //构造函数中初始化上下文对象
} public UnitOfWork()
{
db = new EFDbContext(); //构造函数中初始化上下文对象
} #region Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
db.Dispose();
}
}
disposed = true;
}
#endregion #region Save
public void Save()
{
db.SaveChanges();
}
#endregion #region Repository<T>()
public Repository<T> Repository<T>() where T : BaseEntity
{
if (repositories == null)
{
repositories = new Dictionary<string, object>(); }
var type = typeof(T).Name;//获取当前成员名称
if (!repositories.ContainsKey(type))//如果repositories中不包含Name
{
var repositoryType = typeof(Repository<>);//获取Repository<>类型
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), db);
repositories.Add(type, repositoryInstance); }
return (Repository<T>)repositories[type]; }
#endregion }
}

好了,底层的代码,写完了,现在开始写控制器的代码:我们创建一个Book控制器,进行增删查改。

using EF.Data;
using EF.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc; namespace EF.Web.Controllers
{
public class BookController : Controller
{
//创建并实例化工作单元来
private UnitOfWork unitOfWork = new UnitOfWork();
private Repository<Book> bookRepository; public BookController()
{
//控制器的构造函数中,通过工作单元类的实例,来调用Repository<T>方法,实例化Book仓储
bookRepository = unitOfWork.Repository<Book>();
} #region Index
public ActionResult Index()
{
IEnumerable<Book> lstBooks = bookRepository.Table.ToList();
return View(lstBooks);
}
#endregion #region CreateEditBook
public ActionResult CreateEditBook(int? id)
{
Book model = new Book();
if (id.HasValue) //如果ID有值
{
model = bookRepository.GetById(id.Value);
return View(model);
}
else
{
return View(model);
}
} [HttpPost]
public ActionResult CreateEditBook(Book model)
{
if (model.ID == )//ID为0表示是添加
{
model.AddedDate = DateTime.Now;
model.ModifiedDate = DateTime.Now;
model.IP = Request.UserHostAddress;//获取客户端的主机地址
bookRepository.Insert(model);
}
else //ID不为0,表示是修改
{
Book editModel= bookRepository.GetById(model.ID);
editModel.Author = model.Author;
editModel.ISBN = model.ISBN;
editModel.Title = model.Title;
editModel.PublishedDate = model.PublishedDate;
editModel.ModifiedDate = model.ModifiedDate;
editModel.IP = Request.UserHostAddress;
bookRepository.Update(editModel); }
if (model.ID > )
{
return RedirectToAction("Index");
}
return View(model);
} #endregion #region DeleteBook
public ActionResult DeleteBook(int id)
{
Book model = bookRepository.GetById(id);
return View(model);
} [HttpPost, ActionName("DeleteBook")]
public ActionResult ConfirmDeleteBook(int id)
{
Book model = bookRepository.GetById(id);
bookRepository.Delete(model);
return RedirectToAction("Index");
}
#endregion #region DetailBook
public ActionResult DetailBook(int id)
{
Book model = bookRepository.GetById(id);
return View(model);
}
#endregion #region Dispose
protected override void Dispose(bool disposing)
{
unitOfWork.Dispose();
base.Dispose(disposing);
}
#endregion
}
}

Index 视图代码:

@model IEnumerable<EF.Entity.Book>

<div class="book-example panel panel-primary">
<div class="panel-heading panel-head">Books Listing</div>
<div class="panel-body">
<a id="createEditBookModal" href="@Url.Action("CreateEditBook")" class="btn btn-success">
<span class="glyphicon glyphicon-plus"></span>Book
</a> <table class="table" style="margin: 4px">
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Author)
</th>
<th>
@Html.DisplayNameFor(model => model.ISBN)
</th>
<th>
Action
</th> <th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Author)
</td>
<td>
@Html.DisplayFor(modelItem => item.ISBN)
</td>
<td>
@Html.ActionLink("Edit", "CreateEditBook", new { id = item.ID }, new { @class = "btn btn-success" }) |
@Html.ActionLink("Details", "DetailBook", new { id = item.ID }, new { @class = "btn btn-primary" }) |
@Html.ActionLink("Delete", "DeleteBook", new { id = item.ID }, new { @class = "btn btn-danger" })
</td>
</tr>
} </table>
</div>
</div>

CraeteEdit视图代码:

@model EF.Entity.Book

@{
ViewBag.Title = "Create Edit Book";
}
<div class="book-example panel panel-primary">
<div class="panel-heading panel-head">Add / Edit Book</div>
<div class="panel-body">
@using (Html.BeginForm())
{
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.TextBoxFor(model => model.Title, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.TextBoxFor(model => model.ISBN, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.TextBoxFor(model => model.Author, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.TextBoxFor(model => model.Published, new { @class = "form-control datepicker" })
</div>
</div>
<div class="form-group">
<div class="col-lg-8"></div>
<div class="col-lg-3">
@Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-default" })
<button class="btn btn-success" id="btnSubmit" type="submit">
Submit
</button>
</div>
</div>
</div>
}
</div>
</div>
@section scripts 这里的话,在布局页中要添加这个块: @RenderSection("scripts", required: false)
{
<script src="~/Scripts/bootstrap-datepicker.js" type="text/javascript"></script>
<script src="~/Scripts/book-create-edit.js" type="text/javascript"></script>
}

DeleteBook视图:

@model EF.Entity.Book

@{
ViewBag.Title = "Delete Book";
} <div class="book-example panel panel-primary">
<div class="panel-heading panel-head">Delete Book</div>
<div class="panel-body">
<h3>Are you sure you want to delete this?</h3>
<h1>@ViewBag.ErrorMessage</h1>
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Title, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Author, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.ISBN, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Published, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.IP, new { @class = "form-control" })
</div>
</div> @using (Html.BeginForm())
{
<div class="form-group">
<div class="col-lg-1"></div>
<div class="col-lg-9">
<input type="submit" value="Delete" class="btn btn-danger" />
@Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" })
</div>
</div>
}
</div>
</div>
</div>

Detail视图:

@model EF.Entity.Book
@{
ViewBag.Title = "Detail Book";
}
<div class="book-example panel panel-primary">
<div class="panel-heading panel-head">Book Detail</div>
<div class="panel-body">
<div class="form-horizontal">
<div class="form-group">
@Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Title, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Author, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.ISBN, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.Published, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" })
</div>
</div> <div class="form-group">
@Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" })
<div class="col-lg-9">
@Html.DisplayFor(model => model.IP, new { @class = "form-control" })
</div>
</div> @using (Html.BeginForm())
{
<div class="form-group">
<div class="col-lg-1"></div>
<div class="col-lg-9">
@Html.ActionLink("Edit", "CreateEditBook", new { id = Model.ID }, new { @class = "btn btn-primary" })
@Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" })
</div>
</div>
}
</div>
</div>
</div>

修改一下默认路由为Book控制器,Index方法,然后运行项目》》》

后记:

用到的两个js文件

book-create-edit.js

(function ($) {
function Book() {
var $this = this;
function initializeAddEditBook() {
$('.datepicker').datepicker({
"setDate": new Date(),
"autoclose": true
});
}
$this.init = function () {
initializeAddEditBook();
}
}
$(function () {
var self = new Book();
self.init();
});
}(jQuery))

bootstrap-datepicker.js

/* =========================================================
* bootstrap-datepicker.js
* Repo: https://github.com/eternicode/bootstrap-datepicker/
* Demo: http://eternicode.github.io/bootstrap-datepicker/
* Docs: http://bootstrap-datepicker.readthedocs.org/
* Forked from http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Started by Stefan Petre; improvements by Andrew Rowls + contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */ (function($, undefined){ var $window = $(window); function UTCDate(){
return new Date(Date.UTC.apply(Date, arguments));
}
function UTCToday(){
var today = new Date();
return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
}
function alias(method){
return function(){
return this[method].apply(this, arguments);
};
} var DateArray = (function(){
var extras = {
get: function(i){
return this.slice(i)[];
},
contains: function(d){
// Array.indexOf is not cross-browser;
// $.inArray doesn't work with Dates
var val = d && d.valueOf();
for (var i=, l=this.length; i < l; i++)
if (this[i].valueOf() === val)
return i;
return -;
},
remove: function(i){
this.splice(i,);
},
replace: function(new_array){
if (!new_array)
return;
if (!$.isArray(new_array))
new_array = [new_array];
this.clear();
this.push.apply(this, new_array);
},
clear: function(){
this.splice();
},
copy: function(){
var a = new DateArray();
a.replace(this);
return a;
}
}; return function(){
var a = [];
a.push.apply(a, arguments);
$.extend(a, extras);
return a;
};
})(); // Picker object var Datepicker = function(element, options){
this.dates = new DateArray();
this.viewDate = UTCToday();
this.focusDate = null; this._process_options(options); this.element = $(element);
this.isInline = false;
this.isInput = this.element.is('input');
this.component = this.element.is('.date') ? this.element.find('.add-on, .input-group-addon, .btn') : false;
this.hasInput = this.component && this.element.find('input').length;
if (this.component && this.component.length === )
this.component = false; this.picker = $(DPGlobal.template);
this._buildEvents();
this._attachEvents(); if (this.isInline){
this.picker.addClass('datepicker-inline').appendTo(this.element);
}
else {
this.picker.addClass('datepicker-dropdown dropdown-menu');
} if (this.o.rtl){
this.picker.addClass('datepicker-rtl');
} this.viewMode = this.o.startView; if (this.o.calendarWeeks)
this.picker.find('tfoot th.today')
.attr('colspan', function(i, val){
return parseInt(val) + ;
}); this._allow_update = false; this.setStartDate(this._o.startDate);
this.setEndDate(this._o.endDate);
this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); this.fillDow();
this.fillMonths(); this._allow_update = true; this.update();
this.showMode(); if (this.isInline){
this.show();
}
}; Datepicker.prototype = {
constructor: Datepicker, _process_options: function(opts){
// Store raw options for reference
this._o = $.extend({}, this._o, opts);
// Processed options
var o = this.o = $.extend({}, this._o); // Check if "de-DE" style date is available, if not language should
// fallback to 2 letter code eg "de"
var lang = o.language;
if (!dates[lang]){
lang = lang.split('-')[];
if (!dates[lang])
lang = defaults.language;
}
o.language = lang; switch (o.startView){
case :
case 'decade':
o.startView = ;
break;
case :
case 'year':
o.startView = ;
break;
default:
o.startView = ;
} switch (o.minViewMode){
case :
case 'months':
o.minViewMode = ;
break;
case :
case 'years':
o.minViewMode = ;
break;
default:
o.minViewMode = ;
} o.startView = Math.max(o.startView, o.minViewMode); // true, false, or Number > 0
if (o.multidate !== true){
o.multidate = Number(o.multidate) || false;
if (o.multidate !== false)
o.multidate = Math.max(, o.multidate);
else
o.multidate = ;
}
o.multidateSeparator = String(o.multidateSeparator); o.weekStart %= ;
o.weekEnd = ((o.weekStart + ) % ); var format = DPGlobal.parseFormat(o.format);
if (o.startDate !== -Infinity){
if (!!o.startDate){
if (o.startDate instanceof Date)
o.startDate = this._local_to_utc(this._zero_time(o.startDate));
else
o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
}
else {
o.startDate = -Infinity;
}
}
if (o.endDate !== Infinity){
if (!!o.endDate){
if (o.endDate instanceof Date)
o.endDate = this._local_to_utc(this._zero_time(o.endDate));
else
o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
}
else {
o.endDate = Infinity;
}
} o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
if (!$.isArray(o.daysOfWeekDisabled))
o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function(d){
return parseInt(d, );
}); var plc = String(o.orientation).toLowerCase().split(/\s+/g),
_plc = o.orientation.toLowerCase();
plc = $.grep(plc, function(word){
return (/^auto|left|right|top|bottom$/).test(word);
});
o.orientation = {x: 'auto', y: 'auto'};
if (!_plc || _plc === 'auto')
; // no action
else if (plc.length === ){
switch (plc[]){
case 'top':
case 'bottom':
o.orientation.y = plc[];
break;
case 'left':
case 'right':
o.orientation.x = plc[];
break;
}
}
else {
_plc = $.grep(plc, function(word){
return (/^left|right$/).test(word);
});
o.orientation.x = _plc[] || 'auto'; _plc = $.grep(plc, function(word){
return (/^top|bottom$/).test(word);
});
o.orientation.y = _plc[] || 'auto';
}
},
_events: [],
_secondaryEvents: [],
_applyEvents: function(evs){
for (var i=, el, ch, ev; i < evs.length; i++){
el = evs[i][];
if (evs[i].length === ){
ch = undefined;
ev = evs[i][];
}
else if (evs[i].length === ){
ch = evs[i][];
ev = evs[i][];
}
el.on(ev, ch);
}
},
_unapplyEvents: function(evs){
for (var i=, el, ev, ch; i < evs.length; i++){
el = evs[i][];
if (evs[i].length === ){
ch = undefined;
ev = evs[i][];
}
else if (evs[i].length === ){
ch = evs[i][];
ev = evs[i][];
}
el.off(ev, ch);
}
},
_buildEvents: function(){
if (this.isInput){ // single input
this._events = [
[this.element, {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [,,,,,,,]) === -)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}]
];
}
else if (this.component && this.hasInput){ // component: input + button
this._events = [
// For components that are not readonly, allow keyboard nav
[this.element.find('input'), {
focus: $.proxy(this.show, this),
keyup: $.proxy(function(e){
if ($.inArray(e.keyCode, [,,,,,,,]) === -)
this.update();
}, this),
keydown: $.proxy(this.keydown, this)
}],
[this.component, {
click: $.proxy(this.show, this)
}]
];
}
else if (this.element.is('div')){ // inline datepicker
this.isInline = true;
}
else {
this._events = [
[this.element, {
click: $.proxy(this.show, this)
}]
];
}
this._events.push(
// Component: listen for blur on element descendants
[this.element, '*', {
blur: $.proxy(function(e){
this._focused_from = e.target;
}, this)
}],
// Input: listen for blur on element
[this.element, {
blur: $.proxy(function(e){
this._focused_from = e.target;
}, this)
}]
); this._secondaryEvents = [
[this.picker, {
click: $.proxy(this.click, this)
}],
[$(window), {
resize: $.proxy(this.place, this)
}],
[$(document), {
'mousedown touchstart': $.proxy(function(e){
// Clicked outside the datepicker, hide it
if (!(
this.element.is(e.target) ||
this.element.find(e.target).length ||
this.picker.is(e.target) ||
this.picker.find(e.target).length
)){
this.hide();
}
}, this)
}]
];
},
_attachEvents: function(){
this._detachEvents();
this._applyEvents(this._events);
},
_detachEvents: function(){
this._unapplyEvents(this._events);
},
_attachSecondaryEvents: function(){
this._detachSecondaryEvents();
this._applyEvents(this._secondaryEvents);
},
_detachSecondaryEvents: function(){
this._unapplyEvents(this._secondaryEvents);
},
_trigger: function(event, altdate){
var date = altdate || this.dates.get(-),
local_date = this._utc_to_local(date); this.element.trigger({
type: event,
date: local_date,
dates: $.map(this.dates, this._utc_to_local),
format: $.proxy(function(ix, format){
if (arguments.length === ){
ix = this.dates.length - ;
format = this.o.format;
}
else if (typeof ix === 'string'){
format = ix;
ix = this.dates.length - ;
}
format = format || this.o.format;
var date = this.dates.get(ix);
return DPGlobal.formatDate(date, format, this.o.language);
}, this)
});
}, show: function(){
if (!this.isInline)
this.picker.appendTo('body');
this.picker.show();
this.place();
this._attachSecondaryEvents();
this._trigger('show');
}, hide: function(){
if (this.isInline)
return;
if (!this.picker.is(':visible'))
return;
this.focusDate = null;
this.picker.hide().detach();
this._detachSecondaryEvents();
this.viewMode = this.o.startView;
this.showMode(); if (
this.o.forceParse &&
(
this.isInput && this.element.val() ||
this.hasInput && this.element.find('input').val()
)
)
this.setValue();
this._trigger('hide');
}, remove: function(){
this.hide();
this._detachEvents();
this._detachSecondaryEvents();
this.picker.remove();
delete this.element.data().datepicker;
if (!this.isInput){
delete this.element.data().date;
}
}, _utc_to_local: function(utc){
return utc && new Date(utc.getTime() + (utc.getTimezoneOffset()*));
},
_local_to_utc: function(local){
return local && new Date(local.getTime() - (local.getTimezoneOffset()*));
},
_zero_time: function(local){
return local && new Date(local.getFullYear(), local.getMonth(), local.getDate());
},
_zero_utc_time: function(utc){
return utc && new Date(Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate()));
}, getDates: function(){
return $.map(this.dates, this._utc_to_local);
}, getUTCDates: function(){
return $.map(this.dates, function(d){
return new Date(d);
});
}, getDate: function(){
return this._utc_to_local(this.getUTCDate());
}, getUTCDate: function(){
return new Date(this.dates.get(-));
}, setDates: function(){
var args = $.isArray(arguments[]) ? arguments[] : arguments;
this.update.apply(this, args);
this._trigger('changeDate');
this.setValue();
}, setUTCDates: function(){
var args = $.isArray(arguments[]) ? arguments[] : arguments;
this.update.apply(this, $.map(args, this._utc_to_local));
this._trigger('changeDate');
this.setValue();
}, setDate: alias('setDates'),
setUTCDate: alias('setUTCDates'), setValue: function(){
var formatted = this.getFormattedDate();
if (!this.isInput){
if (this.component){
this.element.find('input').val(formatted).change();
}
}
else {
this.element.val(formatted).change();
}
}, getFormattedDate: function(format){
if (format === undefined)
format = this.o.format; var lang = this.o.language;
return $.map(this.dates, function(d){
return DPGlobal.formatDate(d, format, lang);
}).join(this.o.multidateSeparator);
}, setStartDate: function(startDate){
this._process_options({startDate: startDate});
this.update();
this.updateNavArrows();
}, setEndDate: function(endDate){
this._process_options({endDate: endDate});
this.update();
this.updateNavArrows();
}, setDaysOfWeekDisabled: function(daysOfWeekDisabled){
this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
this.update();
this.updateNavArrows();
}, place: function(){
if (this.isInline)
return;
var calendarWidth = this.picker.outerWidth(),
calendarHeight = this.picker.outerHeight(),
visualPadding = ,
windowWidth = $window.width(),
windowHeight = $window.height(),
scrollTop = $window.scrollTop(); var zIndex = parseInt(this.element.parents().filter(function(){
return $(this).css('z-index') !== 'auto';
}).first().css('z-index')) + ;
var offset = this.component ? this.component.parent().offset() : this.element.offset();
var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
var left = offset.left,
top = offset.top; this.picker.removeClass(
'datepicker-orient-top datepicker-orient-bottom '+
'datepicker-orient-right datepicker-orient-left'
); if (this.o.orientation.x !== 'auto'){
this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
if (this.o.orientation.x === 'right')
left -= calendarWidth - width;
}
// auto x orientation is best-placement: if it crosses a window
// edge, fudge it sideways
else {
// Default to left
this.picker.addClass('datepicker-orient-left');
if (offset.left < )
left -= offset.left - visualPadding;
else if (offset.left + calendarWidth > windowWidth)
left = windowWidth - calendarWidth - visualPadding;
} // auto y orientation is best-situation: top or bottom, no fudging,
// decision based on which shows more of the calendar
var yorient = this.o.orientation.y,
top_overflow, bottom_overflow;
if (yorient === 'auto'){
top_overflow = -scrollTop + offset.top - calendarHeight;
bottom_overflow = scrollTop + windowHeight - (offset.top + height + calendarHeight);
if (Math.max(top_overflow, bottom_overflow) === bottom_overflow)
yorient = 'top';
else
yorient = 'bottom';
}
this.picker.addClass('datepicker-orient-' + yorient);
if (yorient === 'top')
top += height;
else
top -= calendarHeight + parseInt(this.picker.css('padding-top')); this.picker.css({
top: top,
left: left,
zIndex: zIndex
});
}, _allow_update: true,
update: function(){
if (!this._allow_update)
return; var oldDates = this.dates.copy(),
dates = [],
fromArgs = false;
if (arguments.length){
$.each(arguments, $.proxy(function(i, date){
if (date instanceof Date)
date = this._local_to_utc(date);
dates.push(date);
}, this));
fromArgs = true;
}
else {
dates = this.isInput
? this.element.val()
: this.element.data('date') || this.element.find('input').val();
if (dates && this.o.multidate)
dates = dates.split(this.o.multidateSeparator);
else
dates = [dates];
delete this.element.data().date;
} dates = $.map(dates, $.proxy(function(date){
return DPGlobal.parseDate(date, this.o.format, this.o.language);
}, this));
dates = $.grep(dates, $.proxy(function(date){
return (
date < this.o.startDate ||
date > this.o.endDate ||
!date
);
}, this), true);
this.dates.replace(dates); if (this.dates.length)
this.viewDate = new Date(this.dates.get(-));
else if (this.viewDate < this.o.startDate)
this.viewDate = new Date(this.o.startDate);
else if (this.viewDate > this.o.endDate)
this.viewDate = new Date(this.o.endDate); if (fromArgs){
// setting date by clicking
this.setValue();
}
else if (dates.length){
// setting date by typing
if (String(oldDates) !== String(this.dates))
this._trigger('changeDate');
}
if (!this.dates.length && oldDates.length)
this._trigger('clearDate'); this.fill();
}, fillDow: function(){
var dowCnt = this.o.weekStart,
html = '<tr>';
if (this.o.calendarWeeks){
var cell = '<th class="cw">&nbsp;</th>';
html += cell;
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
}
while (dowCnt < this.o.weekStart + ){
html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%]+'</th>';
}
html += '</tr>';
this.picker.find('.datepicker-days thead').append(html);
}, fillMonths: function(){
var html = '',
i = ;
while (i < ){
html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
}
this.picker.find('.datepicker-months td').html(html);
}, setRange: function(range){
if (!range || !range.length)
delete this.range;
else
this.range = $.map(range, function(d){
return d.valueOf();
});
this.fill();
}, getClassNames: function(date){
var cls = [],
year = this.viewDate.getUTCFullYear(),
month = this.viewDate.getUTCMonth(),
today = new Date();
if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){
cls.push('old');
}
else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){
cls.push('new');
}
if (this.focusDate && date.valueOf() === this.focusDate.valueOf())
cls.push('focused');
// Compare internal UTC date with local today, not UTC today
if (this.o.todayHighlight &&
date.getUTCFullYear() === today.getFullYear() &&
date.getUTCMonth() === today.getMonth() &&
date.getUTCDate() === today.getDate()){
cls.push('today');
}
if (this.dates.contains(date) !== -)
cls.push('active');
if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -){
cls.push('disabled');
}
if (this.range){
if (date > this.range[] && date < this.range[this.range.length-]){
cls.push('range');
}
if ($.inArray(date.valueOf(), this.range) !== -){
cls.push('selected');
}
}
return cls;
}, fill: function(){
var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth(),
startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
todaytxt = dates[this.o.language].today || dates['en'].today || '',
cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
tooltip;
this.picker.find('.datepicker-days thead th.datepicker-switch')
.text(dates[this.o.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
.text(todaytxt)
.toggle(this.o.todayBtn !== false);
this.picker.find('tfoot th.clear')
.text(cleartxt)
.toggle(this.o.clearBtn !== false);
this.updateNavArrows();
this.fillMonths();
var prevMonth = UTCDate(year, month-, ),
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
prevMonth.setUTCDate(day);
prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + )%);
var nextMonth = new Date(prevMonth);
nextMonth.setUTCDate(nextMonth.getUTCDate() + );
nextMonth = nextMonth.valueOf();
var html = [];
var clsName;
while (prevMonth.valueOf() < nextMonth){
if (prevMonth.getUTCDay() === this.o.weekStart){
html.push('<tr>');
if (this.o.calendarWeeks){
// ISO 8601: First week contains first thursday.
// ISO also states week starts on Monday, but we can be more abstract here.
var
// Start of current week: based on weekstart/current date
ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - ) % * 864e5),
// Thursday of this week
th = new Date(Number(ws) + ( + - ws.getUTCDay()) % * 864e5),
// First Thursday of year, year from thursday
yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), , )) + ( + - yth.getUTCDay())%*864e5),
// Calendar week: ms between thursdays, div ms per day, div 7 days
calWeek = (th - yth) / 864e5 / + ;
html.push('<td class="cw">'+ calWeek +'</td>'); }
}
clsName = this.getClassNames(prevMonth);
clsName.push('day'); if (this.o.beforeShowDay !== $.noop){
var before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
if (before === undefined)
before = {};
else if (typeof(before) === 'boolean')
before = {enabled: before};
else if (typeof(before) === 'string')
before = {classes: before};
if (before.enabled === false)
clsName.push('disabled');
if (before.classes)
clsName = clsName.concat(before.classes.split(/\s+/));
if (before.tooltip)
tooltip = before.tooltip;
} clsName = $.unique(clsName);
html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
if (prevMonth.getUTCDay() === this.o.weekEnd){
html.push('</tr>');
}
prevMonth.setUTCDate(prevMonth.getUTCDate()+);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join('')); var months = this.picker.find('.datepicker-months')
.find('th:eq(1)')
.text(year)
.end()
.find('span').removeClass('active'); $.each(this.dates, function(i, d){
if (d.getUTCFullYear() === year)
months.eq(d.getUTCMonth()).addClass('active');
}); if (year < startYear || year > endYear){
months.addClass('disabled');
}
if (year === startYear){
months.slice(, startMonth).addClass('disabled');
}
if (year === endYear){
months.slice(endMonth+).addClass('disabled');
} html = '';
year = parseInt(year/, ) * ;
var yearCont = this.picker.find('.datepicker-years')
.find('th:eq(1)')
.text(year + '-' + (year + ))
.end()
.find('td');
year -= ;
var years = $.map(this.dates, function(d){
return d.getUTCFullYear();
}),
classes;
for (var i = -; i < ; i++){
classes = ['year'];
if (i === -)
classes.push('old');
else if (i === )
classes.push('new');
if ($.inArray(year, years) !== -)
classes.push('active');
if (year < startYear || year > endYear)
classes.push('disabled');
html += '<span class="' + classes.join(' ') + '">'+year+'</span>';
year += ;
}
yearCont.html(html);
}, updateNavArrows: function(){
if (!this._allow_update)
return; var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth();
switch (this.viewMode){
case :
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()){
this.picker.find('.prev').css({visibility: 'hidden'});
}
else {
this.picker.find('.prev').css({visibility: 'visible'});
}
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()){
this.picker.find('.next').css({visibility: 'hidden'});
}
else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
case :
case :
if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()){
this.picker.find('.prev').css({visibility: 'hidden'});
}
else {
this.picker.find('.prev').css({visibility: 'visible'});
}
if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()){
this.picker.find('.next').css({visibility: 'hidden'});
}
else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
}
}, click: function(e){
e.preventDefault();
var target = $(e.target).closest('span, td, th'),
year, month, day;
if (target.length === ){
switch (target[].nodeName.toLowerCase()){
case 'th':
switch (target[].className){
case 'datepicker-switch':
this.showMode();
break;
case 'prev':
case 'next':
var dir = DPGlobal.modes[this.viewMode].navStep * (target[].className === 'prev' ? - : );
switch (this.viewMode){
case :
this.viewDate = this.moveMonth(this.viewDate, dir);
this._trigger('changeMonth', this.viewDate);
break;
case :
case :
this.viewDate = this.moveYear(this.viewDate, dir);
if (this.viewMode === )
this._trigger('changeYear', this.viewDate);
break;
}
this.fill();
break;
case 'today':
var date = new Date();
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), , , ); this.showMode(-);
var which = this.o.todayBtn === 'linked' ? null : 'view';
this._setDate(date, which);
break;
case 'clear':
var element;
if (this.isInput)
element = this.element;
else if (this.component)
element = this.element.find('input');
if (element)
element.val("").change();
this.update();
this._trigger('changeDate');
if (this.o.autoclose)
this.hide();
break;
}
break;
case 'span':
if (!target.is('.disabled')){
this.viewDate.setUTCDate();
if (target.is('.month')){
day = ;
month = target.parent().find('span').index(target);
year = this.viewDate.getUTCFullYear();
this.viewDate.setUTCMonth(month);
this._trigger('changeMonth', this.viewDate);
if (this.o.minViewMode === ){
this._setDate(UTCDate(year, month, day));
}
}
else {
day = ;
month = ;
year = parseInt(target.text(), )||;
this.viewDate.setUTCFullYear(year);
this._trigger('changeYear', this.viewDate);
if (this.o.minViewMode === ){
this._setDate(UTCDate(year, month, day));
}
}
this.showMode(-);
this.fill();
}
break;
case 'td':
if (target.is('.day') && !target.is('.disabled')){
day = parseInt(target.text(), )||;
year = this.viewDate.getUTCFullYear();
month = this.viewDate.getUTCMonth();
if (target.is('.old')){
if (month === ){
month = ;
year -= ;
}
else {
month -= ;
}
}
else if (target.is('.new')){
if (month === ){
month = ;
year += ;
}
else {
month += ;
}
}
this._setDate(UTCDate(year, month, day));
}
break;
}
}
if (this.picker.is(':visible') && this._focused_from){
$(this._focused_from).focus();
}
delete this._focused_from;
}, _toggle_multidate: function(date){
var ix = this.dates.contains(date);
if (!date){
this.dates.clear();
}
else if (ix !== -){
this.dates.remove(ix);
}
else {
this.dates.push(date);
}
if (typeof this.o.multidate === 'number')
while (this.dates.length > this.o.multidate)
this.dates.remove();
}, _setDate: function(date, which){
if (!which || which === 'date')
this._toggle_multidate(date && new Date(date));
if (!which || which === 'view')
this.viewDate = date && new Date(date); this.fill();
this.setValue();
this._trigger('changeDate');
var element;
if (this.isInput){
element = this.element;
}
else if (this.component){
element = this.element.find('input');
}
if (element){
element.change();
}
if (this.o.autoclose && (!which || which === 'date')){
this.hide();
}
}, moveMonth: function(date, dir){
if (!date)
return undefined;
if (!dir)
return date;
var new_date = new Date(date.valueOf()),
day = new_date.getUTCDate(),
month = new_date.getUTCMonth(),
mag = Math.abs(dir),
new_month, test;
dir = dir > ? : -;
if (mag === ){
test = dir === -
// If going back one month, make sure month is not current month
// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
? function(){
return new_date.getUTCMonth() === month;
}
// If going forward one month, make sure month is as expected
// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
: function(){
return new_date.getUTCMonth() !== new_month;
};
new_month = month + dir;
new_date.setUTCMonth(new_month);
// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
if (new_month < || new_month > )
new_month = (new_month + ) % ;
}
else {
// For magnitudes >1, move one month at a time...
for (var i=; i < mag; i++)
// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
new_date = this.moveMonth(new_date, dir);
// ...then reset the day, keeping it in the new month
new_month = new_date.getUTCMonth();
new_date.setUTCDate(day);
test = function(){
return new_month !== new_date.getUTCMonth();
};
}
// Common date-resetting loop -- if date is beyond end of month, make it
// end of month
while (test()){
new_date.setUTCDate(--day);
new_date.setUTCMonth(new_month);
}
return new_date;
}, moveYear: function(date, dir){
return this.moveMonth(date, dir*);
}, dateWithinRange: function(date){
return date >= this.o.startDate && date <= this.o.endDate;
}, keydown: function(e){
if (this.picker.is(':not(:visible)')){
if (e.keyCode === ) // allow escape to hide and re-show picker
this.show();
return;
}
var dateChanged = false,
dir, newDate, newViewDate,
focusDate = this.focusDate || this.viewDate;
switch (e.keyCode){
case : // escape
if (this.focusDate){
this.focusDate = null;
this.viewDate = this.dates.get(-) || this.viewDate;
this.fill();
}
else
this.hide();
e.preventDefault();
break;
case : // left
case : // right
if (!this.o.keyboardNavigation)
break;
dir = e.keyCode === ? - : ;
if (e.ctrlKey){
newDate = this.moveYear(this.dates.get(-) || UTCToday(), dir);
newViewDate = this.moveYear(focusDate, dir);
this._trigger('changeYear', this.viewDate);
}
else if (e.shiftKey){
newDate = this.moveMonth(this.dates.get(-) || UTCToday(), dir);
newViewDate = this.moveMonth(focusDate, dir);
this._trigger('changeMonth', this.viewDate);
}
else {
newDate = new Date(this.dates.get(-) || UTCToday());
newDate.setUTCDate(newDate.getUTCDate() + dir);
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir);
}
if (this.dateWithinRange(newDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
e.preventDefault();
}
break;
case : // up
case : // down
if (!this.o.keyboardNavigation)
break;
dir = e.keyCode === ? - : ;
if (e.ctrlKey){
newDate = this.moveYear(this.dates.get(-) || UTCToday(), dir);
newViewDate = this.moveYear(focusDate, dir);
this._trigger('changeYear', this.viewDate);
}
else if (e.shiftKey){
newDate = this.moveMonth(this.dates.get(-) || UTCToday(), dir);
newViewDate = this.moveMonth(focusDate, dir);
this._trigger('changeMonth', this.viewDate);
}
else {
newDate = new Date(this.dates.get(-) || UTCToday());
newDate.setUTCDate(newDate.getUTCDate() + dir * );
newViewDate = new Date(focusDate);
newViewDate.setUTCDate(focusDate.getUTCDate() + dir * );
}
if (this.dateWithinRange(newDate)){
this.focusDate = this.viewDate = newViewDate;
this.setValue();
this.fill();
e.preventDefault();
}
break;
case : // spacebar
// Spacebar is used in manually typing dates in some formats.
// As such, its behavior should not be hijacked.
break;
case : // enter
focusDate = this.focusDate || this.dates.get(-) || this.viewDate;
this._toggle_multidate(focusDate);
dateChanged = true;
this.focusDate = null;
this.viewDate = this.dates.get(-) || this.viewDate;
this.setValue();
this.fill();
if (this.picker.is(':visible')){
e.preventDefault();
if (this.o.autoclose)
this.hide();
}
break;
case : // tab
this.focusDate = null;
this.viewDate = this.dates.get(-) || this.viewDate;
this.fill();
this.hide();
break;
}
if (dateChanged){
if (this.dates.length)
this._trigger('changeDate');
else
this._trigger('clearDate');
var element;
if (this.isInput){
element = this.element;
}
else if (this.component){
element = this.element.find('input');
}
if (element){
element.change();
}
}
}, showMode: function(dir){
if (dir){
this.viewMode = Math.max(this.o.minViewMode, Math.min(, this.viewMode + dir));
}
this.picker
.find('>div')
.hide()
.filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName)
.css('display', 'block');
this.updateNavArrows();
}
}; var DateRangePicker = function(element, options){
this.element = $(element);
this.inputs = $.map(options.inputs, function(i){
return i.jquery ? i[] : i;
});
delete options.inputs; $(this.inputs)
.datepicker(options)
.bind('changeDate', $.proxy(this.dateUpdated, this)); this.pickers = $.map(this.inputs, function(i){
return $(i).data('datepicker');
});
this.updateDates();
};
DateRangePicker.prototype = {
updateDates: function(){
this.dates = $.map(this.pickers, function(i){
return i.getUTCDate();
});
this.updateRanges();
},
updateRanges: function(){
var range = $.map(this.dates, function(d){
return d.valueOf();
});
$.each(this.pickers, function(i, p){
p.setRange(range);
});
},
dateUpdated: function(e){
// `this.updating` is a workaround for preventing infinite recursion
// between `changeDate` triggering and `setUTCDate` calling. Until
// there is a better mechanism.
if (this.updating)
return;
this.updating = true; var dp = $(e.target).data('datepicker'),
new_date = dp.getUTCDate(),
i = $.inArray(e.target, this.inputs),
l = this.inputs.length;
if (i === -)
return; $.each(this.pickers, function(i, p){
if (!p.getUTCDate())
p.setUTCDate(new_date);
}); if (new_date < this.dates[i]){
// Date being moved earlier/left
while (i >= && new_date < this.dates[i]){
this.pickers[i--].setUTCDate(new_date);
}
}
else if (new_date > this.dates[i]){
// Date being moved later/right
while (i < l && new_date > this.dates[i]){
this.pickers[i++].setUTCDate(new_date);
}
}
this.updateDates(); delete this.updating;
},
remove: function(){
$.map(this.pickers, function(p){ p.remove(); });
delete this.element.data().datepicker;
}
}; function opts_from_el(el, prefix){
// Derive options from element data-attrs
var data = $(el).data(),
out = {}, inkey,
replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])');
prefix = new RegExp('^' + prefix.toLowerCase());
function re_lower(_,a){
return a.toLowerCase();
}
for (var key in data)
if (prefix.test(key)){
inkey = key.replace(replace, re_lower);
out[inkey] = data[key];
}
return out;
} function opts_from_locale(lang){
// Derive options from locale plugins
var out = {};
// Check if "de-DE" style date is available, if not language should
// fallback to 2 letter code eg "de"
if (!dates[lang]){
lang = lang.split('-')[];
if (!dates[lang])
return;
}
var d = dates[lang];
$.each(locale_opts, function(i,k){
if (k in d)
out[k] = d[k];
});
return out;
} var old = $.fn.datepicker;
$.fn.datepicker = function(option){
var args = Array.apply(null, arguments);
args.shift();
var internal_return;
this.each(function(){
var $this = $(this),
data = $this.data('datepicker'),
options = typeof option === 'object' && option;
if (!data){
var elopts = opts_from_el(this, 'date'),
// Preliminary otions
xopts = $.extend({}, defaults, elopts, options),
locopts = opts_from_locale(xopts.language),
// Options priority: js args, data-attrs, locales, defaults
opts = $.extend({}, defaults, locopts, elopts, options);
if ($this.is('.input-daterange') || opts.inputs){
var ropts = {
inputs: opts.inputs || $this.find('input').toArray()
};
$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
}
else {
$this.data('datepicker', (data = new Datepicker(this, opts)));
}
}
if (typeof option === 'string' && typeof data[option] === 'function'){
internal_return = data[option].apply(data, args);
if (internal_return !== undefined)
return false;
}
});
if (internal_return !== undefined)
return internal_return;
else
return this;
}; var defaults = $.fn.datepicker.defaults = {
autoclose: false,
beforeShowDay: $.noop,
calendarWeeks: false,
clearBtn: false,
daysOfWeekDisabled: [],
endDate: Infinity,
forceParse: true,
format: 'mm/dd/yyyy',
keyboardNavigation: true,
language: 'en',
minViewMode: ,
multidate: false,
multidateSeparator: ',',
orientation: "auto",
rtl: false,
startDate: -Infinity,
startView: ,
todayBtn: false,
todayHighlight: false,
weekStart:
};
var locale_opts = $.fn.datepicker.locale_opts = [
'format',
'rtl',
'weekStart'
];
$.fn.datepicker.Constructor = Datepicker;
var dates = $.fn.datepicker.dates = {
en: {
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
today: "Today",
clear: "Clear"
}
}; var DPGlobal = {
modes: [
{
clsName: 'days',
navFnc: 'Month',
navStep:
},
{
clsName: 'months',
navFnc: 'FullYear',
navStep:
},
{
clsName: 'years',
navFnc: 'FullYear',
navStep:
}],
isLeapYear: function(year){
return (((year % === ) && (year % !== )) || (year % === ));
},
getDaysInMonth: function(year, month){
return [, (DPGlobal.isLeapYear(year) ? : ), , , , , , , , , , ][month];
},
validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
parseFormat: function(format){
// IE treats \0 as a string end in inputs (truncating the value),
// so it's a bad format delimiter, anyway
var separators = format.replace(this.validParts, '\0').split('\0'),
parts = format.match(this.validParts);
if (!separators || !separators.length || !parts || parts.length === ){
throw new Error("Invalid date format.");
}
return {separators: separators, parts: parts};
},
parseDate: function(date, format, language){
if (!date)
return undefined;
if (date instanceof Date)
return date;
if (typeof format === 'string')
format = DPGlobal.parseFormat(format);
var part_re = /([\-+]\d+)([dmwy])/,
parts = date.match(/([\-+]\d+)([dmwy])/g),
part, dir, i;
if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)){
date = new Date();
for (i=; i < parts.length; i++){
part = part_re.exec(parts[i]);
dir = parseInt(part[]);
switch (part[]){
case 'd':
date.setUTCDate(date.getUTCDate() + dir);
break;
case 'm':
date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
break;
case 'w':
date.setUTCDate(date.getUTCDate() + dir * );
break;
case 'y':
date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
break;
}
}
return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), , , );
}
parts = date && date.match(this.nonpunctuation) || [];
date = new Date();
var parsed = {},
setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
setters_map = {
yyyy: function(d,v){
return d.setUTCFullYear(v);
},
yy: function(d,v){
return d.setUTCFullYear(+v);
},
m: function(d,v){
if (isNaN(d))
return d;
v -= ;
while (v < ) v += ;
v %= ;
d.setUTCMonth(v);
while (d.getUTCMonth() !== v)
d.setUTCDate(d.getUTCDate()-);
return d;
},
d: function(d,v){
return d.setUTCDate(v);
}
},
val, filtered;
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
setters_map['dd'] = setters_map['d'];
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), , , );
var fparts = format.parts.slice();
// Remove noop parts
if (parts.length !== fparts.length){
fparts = $(fparts).filter(function(i,p){
return $.inArray(p, setters_order) !== -;
}).toArray();
}
// Process remainder
function match_part(){
var m = this.slice(, parts[i].length),
p = parts[i].slice(, m.length);
return m === p;
}
if (parts.length === fparts.length){
var cnt;
for (i=, cnt = fparts.length; i < cnt; i++){
val = parseInt(parts[i], );
part = fparts[i];
if (isNaN(val)){
switch (part){
case 'MM':
filtered = $(dates[language].months).filter(match_part);
val = $.inArray(filtered[], dates[language].months) + ;
break;
case 'M':
filtered = $(dates[language].monthsShort).filter(match_part);
val = $.inArray(filtered[], dates[language].monthsShort) + ;
break;
}
}
parsed[part] = val;
}
var _date, s;
for (i=; i < setters_order.length; i++){
s = setters_order[i];
if (s in parsed && !isNaN(parsed[s])){
_date = new Date(date);
setters_map[s](_date, parsed[s]);
if (!isNaN(_date))
date = _date;
}
}
}
return date;
},
formatDate: function(date, format, language){
if (!date)
return '';
if (typeof format === 'string')
format = DPGlobal.parseFormat(format);
var val = {
d: date.getUTCDate(),
D: dates[language].daysShort[date.getUTCDay()],
DD: dates[language].days[date.getUTCDay()],
m: date.getUTCMonth() + ,
M: dates[language].monthsShort[date.getUTCMonth()],
MM: dates[language].months[date.getUTCMonth()],
yy: date.getUTCFullYear().toString().substring(),
yyyy: date.getUTCFullYear()
};
val.dd = (val.d < ? '' : '') + val.d;
val.mm = (val.m < ? '' : '') + val.m;
date = [];
var seps = $.extend([], format.separators);
for (var i=, cnt = format.parts.length; i <= cnt; i++){
if (seps.length)
date.push(seps.shift());
date.push(val[format.parts[i]]);
}
return date.join('');
},
headTemplate: '<thead>'+
'<tr>'+
'<th class="prev">&laquo;</th>'+
'<th colspan="5" class="datepicker-switch"></th>'+
'<th class="next">&raquo;</th>'+
'</tr>'+
'</thead>',
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
footTemplate: '<tfoot>'+
'<tr>'+
'<th colspan="7" class="today"></th>'+
'</tr>'+
'<tr>'+
'<th colspan="7" class="clear"></th>'+
'</tr>'+
'</tfoot>'
};
DPGlobal.template = '<div class="datepicker">'+
'<div class="datepicker-days">'+
'<table class=" table-condensed">'+
DPGlobal.headTemplate+
'<tbody></tbody>'+
DPGlobal.footTemplate+
'</table>'+
'</div>'+
'<div class="datepicker-months">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
DPGlobal.footTemplate+
'</table>'+
'</div>'+
'<div class="datepicker-years">'+
'<table class="table-condensed">'+
DPGlobal.headTemplate+
DPGlobal.contTemplate+
DPGlobal.footTemplate+
'</table>'+
'</div>'+
'</div>'; $.fn.datepicker.DPGlobal = DPGlobal; /* DATEPICKER NO CONFLICT
* =================== */ $.fn.datepicker.noConflict = function(){
$.fn.datepicker = old;
return this;
}; /* DATEPICKER DATA-API
* ================== */ $(document).on(
'focus.datepicker.data-api click.datepicker.data-api',
'[data-provide="datepicker"]',
function(e){
var $this = $(this);
if ($this.data('datepicker'))
return;
e.preventDefault();
// component click requires us to explicitly show it
$this.datepicker('show');
}
);
$(function(){
$('[data-provide="datepicker-inline"]').datepicker();
}); }(window.jQuery));

总结:使用了标准清理模式,每次操作之后,工作单元对象都被销毁了,再次进行其他操作的时候,又会重新创建对象的实例。

完成之后项目的结构是:

5.在MVC中使用泛型仓储模式和工作单元来进行增删查改的相关教程结束。

《5.在MVC中使用泛型仓储模式和工作单元来进行增删查改.doc》

下载本文的Word格式文档,以方便收藏与打印。