Home > Web Front-end > JS Tutorial > Date selection control based on jQuery_jquery

Date selection control based on jQuery_jquery

WBOY
Release: 2016-05-16 18:43:14
Original
1486 people have browsed it

But there are also some problems. Firstly, drawing the calendar is a bit slow. Secondly, the compatibility is not very good with IE Only. Thirdly, it is not based on jQuery haha.

That’s still the old rule, check the effect before doing it

image image
This is a cooler Ext style.
From the picture above, we can see that this control actually has two views, a date and month view, and a year and month selection view.
1: Let’s start with HTML

Determining HTML for the date control is actually relatively simple, because it is obviously a list data format, and of course it mainly uses table.
The two views are wrapped with two Divs respectively. You can switch the views by controlling the display and hiding of the divs. For the complete HTMl structure, you can use IEDeveloper to take a look at the Demo structure. I took a screenshot myself
image
2: Write CSS based on HTML and renderings
In fact, because it is in Ext style, I directly copy the ext css and images. .
CSS will not be analyzed and code will be uploaded directly.
Because the syntax highlighting in the blog park does not support CSS, I will not post it here. Please give me a download address:
All images used:

btn-arrow btn-arrow-light cal

3: After finishing the CSS, we started writing our javascript.
Up there is a complete code

Copy the code The code is as follows:

;(function($) {
var userAgent = window.navigator.userAgent.toLowerCase();
$.browser.msie8 = $.browser.msie && /msie 8.0/i.test(userAgent);
$.browser.msie7 = $.browser.msie && /msie 7.0/i.test(userAgent);
$.browser.msie6 = !$.browser.msie8 && !$.browser.msie7 && $.browser.msie && /msie 6.0/i.test(userAgent);
Date.prototype.Format = function(format) {
var o = {
"M ": this.getMonth() 1,
"d ": this.getDate(),
"h ": this.getHours(),
"H ": this.getHours(),
"m ": this.getMinutes(),
"s ": this.getSeconds(),
"q ": Math.floor((this.getMonth() 3) / 3),
"w": "0123456".indexOf(this.getDay()),
"S": this.getMilliseconds()
};
if (/(y )/.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" k ")").test(format))
format = format.replace(RegExp.$1,
RegExp.$1.length == 1 ? o[k] :
("00" o[k]).substr(("" o[k]).length));
}
return format;
};
function DateAdd(interval, number, idate) {
number = parseInt(number);
var date;
if (typeof (idate) == "string") {
date = idate.split(/D/);
eval("var date = new Date(" date.join(",") ")");
}
if (typeof (idate) == "object") {
date = new Date(idate.toString());
}
switch (interval) {
case "y": date.setFullYear(date.getFullYear() number); break;
case "m": date.setMonth(date.getMonth() number); break;
case "d": date.setDate(date.getDate() number); break;
case "w": date.setDate(date.getDate() 7 * number); break;
case "h": date.setHours(date.getHours() number); break;
case "n": date.setMinutes(date.getMinutes() number); break;
case "s": date.setSeconds(date.getSeconds() number); break;
case "l": date.setMilliseconds(date.getMilliseconds() number); break;
}
return date;
};
$.fn.datepicker = function(o) {
var def = {
weekStart: 0,
weekName: ["日", "一", "二", "三", "四", "五", "六"], //星期的格式
monthName: ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"], //月份的格式
monthp: "月",
Year: new Date().getFullYear(), //定义年的变量的初始值
Month: new Date().getMonth() 1, //定义月的变量的初始值
Day: new Date().getDate(), //定义日的变量的初始值
today: new Date(),
btnOk: " 确定 ",
btnCancel: " 取消 ",
btnToday: "今天",
inputDate: null,
onReturn: false,
version: "1.1",
applyrule: false, //function(){};return rule={startdate,endate};
showtarget: null,
picker: ""
};
$.extend(def, o);
var cp = $("#BBIT_DP_CONTAINER");
if (cp.length == 0) {
var cpHA = [];
cpHA.push("
");
if ($.browser.msie6) {
cpHA.push('');
}
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("
");
//头哟
cpHA.push("
  
");
cpHA.push("
");
//周
cpHA.push("");
//生成周
for (var i = def.weekStart, j = 0; j < 7; j ) {
cpHA.push("");
if (i == 6) { i = 0; } else { i ; }
}
cpHA.push("");
//生成tBody,需要重新生成的
cpHA.push("
", def.weekName[i], "
");
//生成tBody结束
cpHA.push("
");
//输出下来框
cpHA.push("
");
cpHA.push("");
//1月,7月 按钮两个
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("");
cpHA.push("
", def.monthName[0], "", def.monthName[6], "
", def.monthName[1], "", def.monthName[7], "
", def.monthName[2], "", def.monthName[8], "
", def.monthName[3], "", def.monthName[9], "
", def.monthName[4], "", def.monthName[10], "
", def.monthName[5], "", def.monthName[11], "
");
cpHA.push("
");
cpHA.push("
");
var s = cpHA.join("");
$(document.body).append(s);
var cp = $("#BBIT_DP_CONTAINER");
initevents();
}
function initevents() {
//1 today btn;
$("#BBIT-DP-TODAY").click(returntoday);
cp.click(returnfalse);
$("#BBIT_DP_INNER tbody").click(tbhandler);
$("#BBIT_DP_LEFTBTN").click(prevm);
$("#BBIT_DP_RIGHTBTN").click(nextm);
$("#BBIT_DP_YMBTN").click(showym);
$("#BBIT-DP-MP").click(mpclick);
$("#BBIT-DP-MP-PREV").click(mpprevy);
$("#BBIT-DP-MP-NEXT").click(mpnexty);
$("#BBIT-DP-MP-OKBTN").click(mpok);
$("#BBIT-DP-MP-CANCELBTN").click(mpcancel);
}
function mpcancel() {
$("#BBIT-DP-MP").animate({ top: -193 }, { duration: 200, complete: function() { $("#BBIT-DP-MP").hide(); } });
return false;
}
function mpok() {
def.Year = def.cy;
def.Month = def.cm 1;
def.Day = 1;
$("#BBIT-DP-MP").animate({ top: -193 }, { duration: 200, complete: function() { $("#BBIT-DP-MP").hide(); } });
writecb();
return false;
}
function mpprevy() {
var y = def.ty - 10
def.ty = y;
rryear(y);
return false;
}
function mpnexty() {
var y = def.ty 10
def.ty = y;
rryear(y);
return false;
}
function rryear(y) {
var s = y - 4;
var ar = [];
for (var i = 0; i < 5; i ) {
ar.push(s i);
ar.push(s i 5);
}
$("#BBIT-DP-MP td.bbit-dp-mp-year").each(function(i) {
if (def.Year == ar[i]) {
$(this).addClass("bbit-dp-mp-sel");
}
else {
$(this).removeClass("bbit-dp-mp-sel");
}
$(this).html("" ar[i] "").attr("xyear", ar[i]);
});
}
function mpclick(e) {
var panel = $(this);
var et = e.target || e.srcElement;
var td = getTd(et);
if (td == null) {
return false;
}
if ($(td).hasClass("bbit-dp-mp-month")) {
if (!$(td).hasClass("bbit-dp-mp-sel")) {
var ctd = panel.find("td.bbit-dp-mp-month.bbit-dp-mp-sel");
if (ctd.length > 0) {
ctd.removeClass("bbit-dp-mp-sel");
}
$(td).addClass("bbit-dp-mp-sel")
def.cm = parseInt($(td).attr("xmonth"));
}
}
if ($(td).hasClass("bbit-dp-mp-year")) {
if (!$(td).hasClass("bbit-dp-mp-sel")) {
var ctd = panel.find("td.bbit-dp-mp-year.bbit-dp-mp-sel");
if (ctd.length > 0) {
ctd.removeClass("bbit-dp-mp-sel");
}
$(td).addClass("bbit-dp-mp-sel")
def.cy = parseInt($(td).attr("xyear"));
}
}
return false;
}
function showym() {
var mp = $("#BBIT-DP-MP");
var y = def.Year;
def.cy = def.ty = y;
var m = def.Month - 1;
def.cm = m;
var ms = $("#BBIT-DP-MP td.bbit-dp-mp-month");
for (var i = ms.length - 1; i >= 0; i--) {
var ch = $(ms[i]).attr("xmonth");
if (ch == m) {
$(ms[i]).addClass("bbit-dp-mp-sel");
}
else {
$(ms[i]).removeClass("bbit-dp-mp-sel");
}
}
rryear(y);
mp.css("top", -193).show().animate({ top: 0 }, { duration: 200 });
}
function getTd(elm) {
if (elm.tagName.toUpperCase() == "TD") {
return elm;
}
else if (elm.tagName.toUpperCase() == "BODY") {
return null;
}
else {
var p = $(elm).parent();
if (p.length > 0) {
if (p[0].tagName.toUpperCase() != "TD") {
return getTd(p[0]);
}
else {
return p[0];
}
}
}
return null;
}
function tbhandler(e) {
var et = e.target || e.srcElement;
var td = getTd(et);
if (td == null) {
return false;
}
var $td = $(td);
if (!$(td).hasClass("bbit-dp-disabled")) {
var s = $td.attr("xdate");
var arrs = s.split("-");
cp.data("indata", new Date(arrs[0], parseInt(arrs[1], 10) - 1, arrs[2]));
returndate();
}
return false;
}
function returnfalse() {
return false;
}
function prevm() {
if (def.Month == 1) {
def.Year--;
def.Month = 12;
}
else {
def.Month--
}
writecb();
return false;
}
function nextm() {
if (def.Month == 12) {
def.Year ;
def.Month = 1;
}
else {
def.Month
}
writecb();
return false;
}
function returntoday() {
cp.data("indata", new Date());
returndate();
}
function returndate() {
var ct = cp.data("ctarget");
var ck = cp.data("cpk");
var re = cp.data("onReturn");
var ndate = cp.data("indata")
var ads = cp.data("ads");
var ade = cp.data("ade");
var dis = false;
if (ads && ndate < ads) {
dis = true;
}
if (ade && ndate > ade) {
dis = true;
}
if (dis) {
return;
}
if (re && jQuery.isFunction(re)) {
re.call(ct[0], cp.data("indata"));
}
else {
ct.val(cp.data("indata").Format("yyyy-MM-dd"));
}
ck.attr("isshow", "0");
cp.removeData("ctarget").removeData("cpk").removeData("indata").removeData("onReturn")
.removeData("ads").removeData("ade");
cp.css("visibility", "hidden");
ct = ck = null;
}
function writecb() {
var tb = $("#BBIT_DP_INNER tbody");
$("#BBIT_DP_YMBTN").html(def.monthName[def.Month - 1] def.monthp " " def.Year);
var firstdate = new Date(def.Year, def.Month - 1, 1);
var diffday = def.weekStart - firstdate.getDay();
var showmonth = def.Month - 1;
if (diffday > 0) {
diffday -= 7;
}
var startdate = DateAdd("d", diffday, firstdate);
var enddate = DateAdd("d", 42, startdate);
var ads = cp.data("ads");
var ade = cp.data("ade");
var bhm = [];
var tds = def.today.Format("yyyy-MM-dd");
var indata = cp.data("indata");
var ins = indata != null ? indata.Format("yyyy-MM-dd") : "";
for (var i = 1; i <= 42; i ) {
if (i % 7 == 1) {
bhm.push("");
}
var ndate = DateAdd("d", i - 1, startdate);
var tdc = [];
var dis = false;
if (ads && ndate < ads) {
dis = true;
}
if (ade && ndate > ade) {
dis = true;
}
if (ndate.getMonth() < showmonth) {
tdc.push("bbit-dp-prevday");
}
else if (ndate.getMonth() > showmonth) {
tdc.push("bbit-dp-nextday");
}
if (dis) {
tdc.push("bbit-dp-disabled");
}
else {
tdc.push("bbit-dp-active");
}
var s = ndate.Format("yyyy-MM-dd");
if (s == tds) {
tdc.push("bbit-dp-today");
}
if (s == ins) {
tdc.push("bbit-dp-selected");
}
bhm.push("", ndate.getDate(), "");
if (i % 7 == 0) {
bhm.push("");
}
}
tb.html(bhm.join(""));
}
var dateReg = /^(d{1,4})(-|/|.)(d{1,2})2(d{1,2})$/;
return $(this).each(function() {
var obj = $(this).addClass("bbit-dp-input");
var picker = $(def.picker);
def.showtarget == null && obj.after(picker);
picker.click(function(e) {
var isshow = $(this).attr("isshow");
//先隐藏
var me = $(this);
if (cp.css("visibility") == "visible") {
cp.css(" visibility", "hidden");
}
if (isshow == "1") {
me.attr("isshow", "0");
cp.removeData("ctarget").removeData("cpk").removeData("indata").removeData("onReturn");
return false;
}
var v = obj.val();
if (v != "") {
v = v.match(dateReg);
}
if (v == null || v == "") {
def.Year = new Date().getFullYear();
def.Month = new Date().getMonth() 1;
def.Day = new Date().getDate();
def.inputDate = null
}
else {
def.Year = parseInt(v[1], 10);
def.Month = parseInt(v[3], 10);
def.Day = parseInt(v[4], 10);
def.inputDate = new Date(def.Year, def.Month - 1, def.Day);
}
cp.data("ctarget", obj).data("cpk", me).data("indata", def.inputDate).data("onReturn", def.onReturn);
if (def.applyrule && $.isFunction(def.applyrule)) {
var rule = def.applyrule.call(obj, obj[0].id);
if (rule) {
if (rule.startdate) {
cp.data("ads", rule.startdate);
}
else {
cp.removeData("ads");
}
if (rule.enddate) {
cp.data("ade", rule.enddate);
}
else {
cp.removeData("ade");
}
}
}
else {
cp.removeData("ads").removeData("ade")
}
writecb();
$("#BBIT-DP-T").height(cp.height());
var t = def.showtarget || obj;
var pos = t.offset();
var height = t.outerHeight();
var newpos = { left: pos.left, top: pos.top height };
var w = cp.width();
var h = cp.height();
var bw = document.documentElement.clientWidth;
var bh = document.documentElement.clientHeight;
if ((newpos.left w) >= bw) {
newpos.left = bw - w - 2;
}
if ((newpos.top h) >= bh) {
newpos.top = pos.top - h - 2;
}
if (newpos.left < 0) {
newpos.left = 10;
}
if (newpos.top < 0) {
newpos.top = 10;
}
$("#BBIT-DP-MP").hide();
newpos.visibility = "visible";
cp.css(newpos);
//cp.show();
$(this).attr("isshow", "1");
$(document).one("click", function(e) {
me.attr("isshow", "0");
cp.removeData("ctarget").removeData("cpk").removeData("indata");
cp.css("visibility", "hidden");
});
return false;
});
});
};
})(jQuery);

那接着就是分析一下实现的主要过程和一些注意的要点:
首先还是套版化编写jQuery控件的套子:
复制代码 代码如下:

;(function($) {
//You can also use $.fn.extend(datepicker:function(o){})
$.fn.datepicker= function(o) {
} ( Comments have been added to the code to explain the meaning of these parameters. Several parameters are set for multi-language, such as weekName, monthName



Copy code
The code is as follows: var def = { weekStart: 0,//The day of the week when the week starts, 0 represents Sunday weekName: ["Day", "Monday" , "two", "three", "four", "five", "six"], //week format
monthName: ["one", "two", "three", "four", " Five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"], //The format of the month
monthp: "month",// The suffix of the month
Year: new Date().getFullYear(), //The initial value of the variable that defines the year
Month: new Date().getMonth() 1, //The initial value of the variable that defines the month
Day: new Date().getDate(), //The initial value of the variable that defines the day
today: new Date(),//today
btnOk: "OK",//OK button Text
btnCancel: "Cancel", //The text of the cancel button
btnToday: "Today", //The text of today's button
inputDate: null, //Useless, it will only be used for storage in the code Data
onReturn: false, //Function called back when a date is selected
version: "1.0", //Version
applyrule: false, //Date selection rules, you can set a selectable date range function (){};return rule={startdate,endate};
showtarget: null, //Display carrier, the object on which the calendar expansion depends, the default is the object itself
picker: "" //Additional click event Object
};
$.extend(def, o);//Use the passed parameters to fill in the default


The second part is to initialize the month view and year and month selection. The HTML of the view is



Copy the code
The code is as follows: //Give the date selection control a special ID, get the object with this ID, and determine if the object exists, use it directly // The HTML of the date uses a singleton, that is, only one copy of HTML is generated on one page var cp = $("#BBIT_DP_CONTAINER" );
if (cp.length == 0) {
var cpHA = []; //The old rule is to use arrays to splice html, and finally use innerHTML to attach to the container to improve performance
cpHA. push("
");
if ($.browser.msie6) { / /If it is IE6 pop-up layer cover select
cpHA.push('');
}
cpHA.push("");
cpHA.push("
");
//头yo
cpHA.push("
< ;em>
");
cpHA.push("
");
//week
cpHA.push ("");
//Generate week
for (var i = def.weekStart, j = 0; j < 7; j ) {
cpHA.push("
", def.weekName[i], "< ;/th>");
if (i == 6) { i = 0; } else { i ; }
}
.....//Omit some codes
cpHA. push("
");
cpHA.push("");
cpHA.push("");
var s = cpHA.join("");
$(document.body).append(s); //Add to body
cp = $("#BBIT_DP_CONTAINER"); //Get it again Once
initevents(); //Initialization event
}


A key point here is that the HTML output and event initialization of the date are only done once, because basically two will not be opened on the same page at the same time. There are also some special custom attributes in the generated HTML. If you look carefully, you will find that these attributes will play a big role in subsequent time processing. So let’s take a look at the event
Copy the code The code is as follows:

$("#BBIT- DP-TODAY").click(returntoday);//Today's button event
cp.click(returnfalse);//Prevent bubbling
$("#BBIT_DP_INNER tbody").click(tbhandler);/ /Add a click event to the body in the middle of the month view instead of adding
$("#BBIT_DP_LEFTBTN").click(prevm) to each td;//Last month
$("#BBIT_DP_RIGHTBTN").click( nextm);//Next month
$("#BBIT_DP_YMBTN").click(showym);//Switch to year and month view
$("#BBIT-DP-MP").click(mpclick) ;//The click event of the year and month view is also used for distribution
$("#BBIT-DP-MP-PREV").click(mpprevy);//Previous year
$("#BBIT- DP-MP-NEXT").click(mpnexty);//Next year
$("#BBIT-DP-MP-OKBTN").click(mpok);//Event of ok button
$ ("#BBIT-DP-MP-CANCELBTN").click(mpcancel);//Cancel button event

Add events to each element that needs to be clicked. There are two places here. It is quite special. For a click view of a monthly view, the traditional method is to add events to each TD. However, my TD does not exist at this time. However, if an event is added every time a TD is generated, then the impact will be Performance, so we directly add a click event to the container and distribute the event by judging the event source. Selecting the view in another year and month also has the same logic as above, so let's
analyze the click event of the month view. , in fact, when each TD is generated, an xdate custom attribute image will be registered. Let’s take a look at the tbhandler function
Copy code The code is as follows:

function tbhandler(e) {
var et = e.target || e.srcElement; //Find the event source
var td = getTd(et); //The event source recursively looks up td
if (td == null) {
return false;
}
var $td = $(td);
.
if (!$(td).hasClass("bbit-dp-disabled")) {If it is not disabled
var s = $td.attr("xdate");//Get the self value of td Define attribute date data
var arrs = s.split("-");
cp.data("indata", new Date(arrs[0], parseInt(arrs[1], 10) - 1, arrs[2]));
returndate();//Return date
.
}
return false;
}

All date selection times After initialization (one-time), you need to add click events to each picker
Copy the code The code is as follows:

return $(this).each(function() {
var obj = $(this).addClass("bbit-dp-input");//Add style to input
var picker = $(def.picker);//Get the picker object
//If showtarget is not null, this will register the picker behind the input
//Otherwise the user handles the picker position by himself, that is, picker It already exists on the page itself
//You can look at the difference between calls 1 and 3 in the example
def.showtarget == null && obj.after(picker);
picker.click(function( e) {
....//Omit code
});

The click event of picker is relatively long. I think it is better to talk about it separately. The first point is The second is the processing of real-life hidden events, the second is the processing of window edge issues, and the third is the processing of date range rules.
Copy code The code is as follows:

function(e) {
//Get whether it is currently displayed
var isshow = $(this).attr("isshow");
var me = $(this);
//If displayed, then hidden, used to handle the logic of clicking the picker to display it and then clicking the picker to hide it
if (cp.css("visibility") == "visible") {
cp. css(" visibility", "hidden");
}
//Similarly if it is displayed
if (isshow == "1") {
me.attr("isshow", " 0");
//Remover temporary data, because it is a singleton, it needs to indicate which input is currently
cp.removeData("ctarget").removeData("cpk").removeData("indata"). removeData("onReturn");
return false; //Prevent bubbling
}
//If hidden, get the input value
var v = obj.val();
if (v != "") {
v = v.match(dateReg);//Verify whether the format is correct
}
if (v == null || v == "") { //If the format is incorrect or empty, use the current date
def.Year = new Date().getFullYear();
def.Month = new Date().getMonth() 1;
def. Day = new Date().getDate();
def.inputDate = null
}
else {
//Otherwise use the input date
def.Year = parseInt(v[1 ], 10);
def.Month = parseInt(v[3], 10);
def.Day = parseInt(v[4], 10);
def.inputDate = new Date(def .Year, def.Month - 1, def.Day);
}
//Register temporary data
cp.data("ctarget", obj).data("cpk", me).data ("indata", def.inputDate).data("onReturn", def.onReturn);
//Call the rule and return an optional date range
if (def.applyrule && $.isFunction(def. applyrule)) {
var rule = def.applyrule.call(obj, obj[0].id);
if (rule) {
if (rule.startdate) {
cp.data ("ads", rule.startdate);
}
else {
cp.removeData("ads");
}
if (rule.enddate) {
cp. data("ade", rule.enddate);
}
else {
cp.removeData("ade");
}
}
}
else {
//Remove the restriction if it does not exist
cp.removeData("ads").removeData("ade")
}
//The content of the monthly calendar is td
writecb();
$("#BBIT-DP-T").height(cp.height());
//Get the displayed attached object
var t = def.showtarget || obj;
/ /Get the position of the object
var pos = t.offset();
//Get the height of the object
var height = t.outerHeight();
//The position of the date selection box is attached The position of the object plus its height
var newpos = { left: pos.left, top: pos.top height };
//The following are all dealing with window boundary issues
var w = cp.width( );
var h = cp.height();
var bw = document.documentElement.clientWidth;
var bh = document.documentElement.clientHeight;
if ((newpos.left w) > ;= bw) {
newpos.left = bw - w - 2;
}
if ((newpos.top h) >= bh) {
newpos.top = pos.top - h - 2;
}
if (newpos.left < 0) {
newpos.left = 10;
}
if (newpos.top < 0) {
newpos.top = 10;
}
//Force the default to be the month date view
$("#BBIT-DP-MP").hide();
newpos.visibility = "visible" ;
cp.css(newpos); //Move to known position and display
//cp.show();
$(this).attr("isshow", "1");
//Register a single click event to the document to solve the problem of hiding the date picker by clicking elsewhere after opening the date picker
$(document).one("click", function(e) {
me.attr("isshow", "0");
cp.removeData("ctarget").removeData("cpk").removeData("indata");
cp.css("visibility ", "hidden");
});
return false;//Organization bubbles
}

Some other codes are functions of date operations, such as last month and next month I won’t introduce it for a while. If you have any questions about the code, you can leave a message and I will answer it. Finally, there are examples.

The first example is an honest demonstration Demo example. There are three types. Method, there is also a description of the calling method: http://jscs.cloudapp.net/ControlsSample/dpdemo
The second example is the datepicker combined with the schedule management control I wrote Application (you can take a look at this first) http://xuanye.cloudapp.net/
The

positions are: image and image

is the application of datepicker in my creation. Finally, if you think this article is helpful to you, then click [Recommend]?
Code packaging script home

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template