1. 程式人生 > >JS元件系列——自己動手擴充套件BootstrapTable的treegrid功能

JS元件系列——自己動手擴充套件BootstrapTable的treegrid功能

前言:上篇  JS元件系列——自己動手封裝bootstrap-treegrid元件 博主自己動手封裝了下treegrid的功能,但畢竟那個元件只是一個單獨針對樹形表格做的,適用性還比較有限。關注博主的園友應該知道,博主的部落格裡面寫了很多bootstrapTable的擴充套件,今天打算在直接在bootstrapTable的基礎上擴充套件一個treegrid的功能,很多長期關注博主部落格的園友一直在問我怎麼在bootstrapTable裡面直接使用treegrid的功能,所以今天還是帶來點福利。有興趣的可以捧個人場!

一、效果預覽

全部摺疊

展開一級

全部展開

二、程式碼示例

怎麼樣?效果還行吧。給出js的原始碼供大家參考。

(function ($) {
    'use strict';

    var sprintf = function (str) {
        var args = arguments,
            flag = true,
            i = 1;

        str = str.replace(/%s/g, function () {
            var arg = args[i++];

            if (typeof arg === 'undefined') {
                flag 
= false; return ''; } return arg; }); return flag ? str : ''; }; var getFieldIndex = function (columns, field) { var index = -1; $.each(columns, function (i, column) { if (column.field === field) { index
= i; return false; } return true; }); return index; }; var calculateObjectValue = function (self, name, args, defaultValue) { var func = name; if (typeof name === 'string') { var names = name.split('.'); if (names.length > 1) { func = window; $.each(names, function (i, f) { func = func[f]; }); } else { func = window[name]; } } if (typeof func === 'object') { return func; } if (typeof func === 'function') { return func.apply(self, args); } if (!func && typeof name === 'string' && sprintf.apply(this, [name].concat(args))) { return sprintf.apply(this, [name].concat(args)); } return defaultValue; }; var getItemField = function (item, field) { var value = item; if (typeof field !== 'string' || item.hasOwnProperty(field)) { return item[field]; } var props = field.split('.'); for (var p in props) { value = value[props[p]]; } return value; }; var getParent = function (node, source, field) { var data = []; var items = $.grep(source, function (item, index) { return node.ParentId == item[field]; }); $.each(items, function (index, item) { data.splice(0, 0, item); var child = getParent(item, source, field); $.each(child, function (i, n) { data.splice(0, 0, n); }); }); return data; }; var getChild = function (node, source, field) { var data = []; var items = $.grep(source, function (item, index) { return item.ParentId == node[field]; }); $.each(items, function (index, item) { data.push(item); var child = getChild(item, source, field); $.each(child, function (i, n) { data.push(n); }); }); return data; }; //呼叫bootstrapTable元件的構造器得到物件 var BootstrapTable = $.fn.bootstrapTable.Constructor, _initData = BootstrapTable.prototype.initData, _initPagination = BootstrapTable.prototype.initPagination, _initBody = BootstrapTable.prototype.initBody; //重寫bootstrapTable的initData方法 BootstrapTable.prototype.initData = function () { _initData.apply(this, Array.prototype.slice.apply(arguments)); var that = this; if (that.options.treeView && this.data.length > 0) { var rows = []; var roots = $.grep(this.data, function (row, index) { return row.Level == that.options.treeRootLevel; }); $.each(roots, function (index, item) { rows.push(item); var child = getChild(item, that.data, that.options.treeId); $.each(child, function (i, n) { if (that.options.treeCollapseAll) { n.hidden = true; } rows.push(n); }); }); that.options.data = that.data = rows; } }; //重寫bootstrapTable的initPagination方法 BootstrapTable.prototype.initPagination = function () { //理論情況下,treegrid是不支援分頁的,所以預設分頁引數為false this.options.pagination = false; //呼叫“父類”的“虛方法” _initPagination.apply(this, Array.prototype.slice.apply(arguments)); }; //重寫bootstrapTable的initBody方法 BootstrapTable.prototype.initBody = function (fixedScroll) { var that = this, html = [], data = this.getData(); this.trigger('pre-body', data); this.$body = this.$el.find('tbody'); if (!this.$body.length) { this.$body = $('<tbody></tbody>').appendTo(this.$el); } if (!this.options.pagination || this.options.sidePagination === 'server') { this.pageFrom = 1; this.pageTo = data.length; } for (var i = this.pageFrom - 1; i < this.pageTo; i++) { var key, item = data[i], style = {}, csses = [], data_ = '', attributes = {}, htmlAttributes = []; if (item.hidden) continue; style = calculateObjectValue(this.options, this.options.rowStyle, [item, i], style); if (style && style.css) { for (key in style.css) { csses.push(key + ': ' + style.css[key]); } } attributes = calculateObjectValue(this.options, this.options.rowAttributes, [item, i], attributes); if (attributes) { for (key in attributes) { htmlAttributes.push(sprintf('%s="%s"', key, escapeHTML(attributes[key]))); } } if (item._data && !$.isEmptyObject(item._data)) { $.each(item._data, function (k, v) { if (k === 'index') { return; } data_ += sprintf(' data-%s="%s"', k, v); }); } html.push('<tr', sprintf(' %s', htmlAttributes.join(' ')), sprintf(' id="%s"', $.isArray(item) ? undefined : item._id), sprintf(' class="%s"', style.classes || ($.isArray(item) ? undefined : item._class)), sprintf(' data-index="%s"', i), sprintf(' data-uniqueid="%s"', item[this.options.uniqueId]), sprintf('%s', data_), '>' ); if (this.options.cardView) { html.push(sprintf('<td colspan="%s">', this.header.fields.length)); } if (!this.options.cardView && this.options.detailView) { html.push('<td>', '<a class="detail-icon" href="javascript:">', sprintf('<i class="%s %s"></i>', this.options.iconsPrefix, this.options.icons.detailOpen), '</a>', '</td>'); } $.each(this.header.fields, function (j, field) { var text = '', value = getItemField(item, field), type = '', cellStyle = {}, id_ = '', class_ = that.header.classes[j], data_ = '', rowspan_ = '', title_ = '', column = that.columns[getFieldIndex(that.columns, field)]; if (!column.visible) { return; } style = sprintf('style="%s"', csses.concat(that.header.styles[j]).join('; ')); value = calculateObjectValue(column, that.header.formatters[j], [value, item, i], value); if (item['_' + field + '_id']) { id_ = sprintf(' id="%s"', item['_' + field + '_id']); } if (item['_' + field + '_class']) { class_ = sprintf(' class="%s"', item['_' + field + '_class']); } if (item['_' + field + '_rowspan']) { rowspan_ = sprintf(' rowspan="%s"', item['_' + field + '_rowspan']); } if (item['_' + field + '_title']) { title_ = sprintf(' title="%s"', item['_' + field + '_title']); } cellStyle = calculateObjectValue(that.header, that.header.cellStyles[j], [value, item, i], cellStyle); if (cellStyle.classes) { class_ = sprintf(' class="%s"', cellStyle.classes); } if (cellStyle.css) { var csses_ = []; for (var key in cellStyle.css) { csses_.push(key + ': ' + cellStyle.css[key]); } style = sprintf('style="%s"', csses_.concat(that.header.styles[j]).join('; ')); } if (item['_' + field + '_data'] && !$.isEmptyObject(item['_' + field + '_data'])) { $.each(item['_' + field + '_data'], function (k, v) { if (k === 'index') { return; } data_ += sprintf(' data-%s="%s"', k, v); }); } if (column.checkbox || column.radio) { type = column.checkbox ? 'checkbox' : type; type = column.radio ? 'radio' : type; text = [that.options.cardView ? '<div class="card-view">' : '<td class="bs-checkbox">', '<input' + sprintf(' data-index="%s"', i) + sprintf(' name="%s"', that.options.selectItemName) + sprintf(' type="%s"', type) + sprintf(' value="%s"', item[that.options.idField]) + sprintf(' checked="%s"', value === true || (value && value.checked) ? 'checked' : undefined) + sprintf(' disabled="%s"', !column.checkboxEnabled || (value && value.disabled) ? 'disabled' : undefined) + ' />', that.header.formatters[j] && typeof value === 'string' ? value : '', that.options.cardView ? '</div>' : '</td>' ].join(''); item[that.header.stateField] = value === true || (value && value.checked); } else { value = typeof value === 'undefined' || value === null ? that.options.undefinedText : value; var indent, icon; if (that.options.treeView && column.field == that.options.treeField) { var indent = item.Level == that.options.Level ? '' : sprintf('<span style="margin-left: %spx;"></span>', (item.Level - that.options.treeRootLevel) * 15); var child = $.grep(data, function (d, i) { return d.ParentId == item[that.options.treeId] && !d.hidden; }); icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', child.length > 0 ? that.options.expandIcon : that.options.collapseIcon); //icon = sprintf('<span class="tree-icon %s" style="cursor: pointer; margin: 0px 5px;"></span>', child.length > 0 ? that.options.expandIcon : ""); } text = that.options.cardView ? ['<div class="card-view">', that.options.showHeader ? sprintf('<span class="title" %s>%s</span>', style, getPropertyFromOther(that.columns, 'field', 'title', field)) : '', sprintf('<span class="value">%s</span>', value), '</div>' ].join('') : [sprintf('<td%s %s %s %s %s %s>', id_, class_, style, data_, rowspan_, title_), indent, icon, value, '</td>' ].join(''); if (that.options.cardView && that.options.smartDisplay && value === '') { text = ''; } } html.push(text); }); if (this.options.cardView) { html.push('</td>'); } html.push('</tr>'); } if (!html.length) { html.push('<tr class="no-records-found">', sprintf('<td colspan="%s">%s</td>', this.$header.find('th').length, this.options.formatNoMatches()), '</tr>'); } this.$body.html(html.join('')); if (!fixedScroll) { this.scrollTo(0); } this.$body.find('> tr[data-index] > td').off('click dblclick').on('click dblclick', function (e) { var $td = $(this), $tr = $td.parent(), item = that.data[$tr.data('index')], index = $td[0].cellIndex, field = that.header.fields[that.options.detailView && !that.options.cardView ? index - 1 : index], column = that.columns[getFieldIndex(that.columns, field)], value = getItemField(item, field); if ($td.find('.detail-icon').length) { return; } that.trigger(e.type === 'click' ? 'click-cell' : 'dbl-click-cell', field, value, item, $td); that.trigger(e.type === 'click' ? 'click-row' : 'dbl-click-row', item, $tr); if (e.type === 'click' && that.options.clickToSelect && column.clickToSelect) { var $selectItem = $tr.find(sprintf('[name="%s"]', that.options.selectItemName)); if ($selectItem.length) { $selectItem[0].click(); } } }); this.$body.find('> tr[data-index] > td > .detail-icon').off('click').on('click', function () { debugger; var $this = $(this), $tr = $this.parent().parent(), index = $tr.data('index'), row = data[index]; if ($tr.next().is('tr.detail-view')) { $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailOpen)); $tr.next().remove(); that.trigger('collapse-row', index, row); } else { $this.find('i').attr('class', sprintf('%s %s', that.options.iconsPrefix, that.options.icons.detailClose)); $tr.after(sprintf('<tr class="detail-view"><td colspan="%s">%s</td></tr>', $tr.find('td').length, calculateObjectValue(that.options, that.options.detailFormatter, [index, row], ''))); that.trigger('expand-row', index, row, $tr.next().find('td')); } that.resetView(); }); this.$body.find('> tr[data-index] > td > .tree-icon').off('click').on('click', function (e) { debugger; e.stopPropagation(); var $this = $(this), $tr = $this.parent().parent(), index = $tr.data('index'), row = data[index]; var icon = $(this); var child = getChild(data[index], data, that.options.treeId); $.each(child, function (i, c) { $.each(that.data, function (index, item) { if (item[that.options.treeId] == c[that.options.treeId]) { item.hidden = icon.hasClass(that.options.expandIcon); that.uncheck(index); return; } }); }); if (icon.hasClass(that.options.expandIcon)) { icon.removeClass(that.options.expandIcon).addClass(that.options.collapseIcon); } else { icon.removeClass(that.options.collapseIcon).addClass(that.options.expandIcon); } that.options.data = that.data; that.initBody(true); }); this.$selectItem = this.$body.find(sprintf('[name="%s"]', this.options.selectItemName)); this.$selectItem.off('click').on('click', function (event) { event.stopImmediatePropagation(); var $this = $(this), checked = $this.prop('checked'), row = that.data[$this.data('index')]; if (that.options.maintainSelected && $(this).is(':radio')) { $.each(that.options.data, function (i, row) { row[that.header.stateField] = false; }); } row[that.header.stateField] = checked; if (that.options.singleSelect) { that.$selectItem.not(this).each(function () { that.data[$(this).data('index')][that.header.stateField] = false; }); that.$selectItem.filter(':checked').not(this).prop('checked', false); } that.updateSelected(); that.trigger(checked ? 'check' : 'uncheck', row, $this); }); $.each(this.header.events, function (i, events) { if (!events) { return; } if (typeof events === 'string') { events = calculateObjectValue(null, events); } var field = that.header.fields[i], fieldIndex = $.inArray(field, that.getVisibleFields()); if (that.options.detailView && !that.options.cardView) { fieldIndex += 1; } for (var key in events) { that.$body.find('tr').each(function () { var $tr = $(this), $td = $tr.find(that.options.cardView ? '.card-view' : 'td').eq(fieldIndex), index = key.indexOf(' '), name = key.substring(0, index), el = key.substring(index + 1), func = events[key]; $td.find(el).off(name).on(name, function (e) { var index = $tr.data('index'), row = that.data[index], value = row[field]; func.apply(this, [e, value, row, index]); }); }); } }); this.updateSelected(); this.resetView(); this.trigger('post-body'); }; //給元件增加預設引數列表 $.extend($.fn.bootstrapTable.defaults, { treeView: false,//treeView檢視 treeField: "id",//treeView檢視欄位 treeId: "id", treeRootLevel: 0,//根節點序號 treeCollapseAll: false,//是否全部展開 collapseIcon: "glyphicon glyphicon-chevron-right",//摺疊樣式 expandIcon: "glyphicon glyphicon-chevron-down"//展開樣式 }); })(jQuery);

元件的使用如下:

1、首先引用這個js檔案。

2、然後初始化元件

         $('#tb').bootstrapTable({
                    url: ActionUrl + 'GetMenuList',
                    toolbar: '#toolbar',
                    sidePagination: 'client',
                    pagination: false,
                    treeView: true,
                    treeId: "Id",
                    treeField: "Name",
                    treeRootLevel: 1,
                    clickToSelect: true,//collapseIcon: "glyphicon glyphicon-triangle-right",//摺疊樣式
                    //expandIcon: "glyphicon glyphicon-triangle-bottom"//展開樣式
                });

treeView:true表示啟用樹表格模式;

treeId:'Id'表示每一行tree的id;

treeField:'Name'表示要對那一列進行展開;

treeRootLevel:1表示樹根的級別。

還有一個地方需要注意,要建立記錄之間的父子級關係,必然後有一個ParentId的概念,所以在從後端返回的結果集裡面,每條記錄勢必有一個ParentId的屬性,如果是根節點,ParentId為null。比如我們後臺得到的結果集的json格式如下:

[{Id: 1, Name: "系統設定", Url: null, ParentId: null, Level: 1, CreateTime: null, Status: 1, SortOrder: 1,…},
{Id: 2, Name: "選單管理", Url: "/Systems/Menu/Index", ParentId: 1, Level: 2, CreateTime: null, Status: 1,…},
{Id: 3, Name: "訂單管理", Url: null, ParentId: null, Level: 1, CreateTime: "2017-05-31 17:05:27",…},
{Id: 4, Name: "基礎資料", Url: null, ParentId: null, Level: 1, CreateTime: "2017-05-31 17:05:55",…},
{Id: 5, Name: "新增訂單", Url: "/order/add", ParentId: 3, Level: 2, CreateTime: "2017-05-31 17:07:03",…}]

三、元件需要完善的地方

上述封裝給大家提供一個擴充套件bootstrapTable元件treeview功能,還有很多地方需要完善,比如:

1、我們的葉子節點前面的圖示可以去掉;

2、增加展開所有、摺疊所有的功能;

3、Level欄位可以去掉,通過ParentId為null來確定根節點。

有興趣的園友可以自己試試。

四、總結

至此本文就結束了,這篇針對上篇做了一個補充,使得我們可以根據專案的需求自己選擇用哪種方式,如果我們專案使用的是bootstrapTable作為資料展示的元件,可以考慮上述擴充套件;如果沒有使用bootstrapTable,可以試試上篇的元件。如果你覺得本文能夠幫助你,可以右邊隨意 打賞 博主,打賞後可以獲得博主永久免費的技術支援。

歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線,否則保留追究法律責任的權利

相關推薦

JS元件系列——自己動手擴充套件BootstrapTable的 凍結列 功能:徹底解決高度問題

前言:一年前,博主分享過一篇關於bootstrapTable元件凍結列的解決方案  JS元件系列——Bootstrap Table 凍結列功能IE瀏覽器相容性問題解決方案 ,通過該篇,確實可以實現bootstrapTable的凍結列效果,並且可以相容ie瀏覽器。這一年的時間,不斷有園友以及群裡面的朋友問過我關

JS元件系列——自己動手擴充套件BootstrapTable的treegrid功能

前言:上篇  JS元件系列——自己動手封裝bootstrap-treegrid元件 博主自己動手封裝了下treegrid的功能,但畢竟那個元件只是一個單獨針對樹形表格做的,適用性還比較有限。關注博主的園友應該知道,博主的部落格裡面寫了很多bootstrapTable的擴充套件,今天打算在直接在bootstra

JS元件系列——自己動手封裝bootstrap-treegrid元件

前言:最近產品需要設計一套相對完整的組織架構的解決方案,由於組織架構涉及到層級關係,在表格裡面展示層級關係,自然就要用到所謂的treegrid。可惜的是,一些輕量級的表格元件本身並沒有自帶樹形表格的功能,比如bootstrapTable就沒有這個功能,怎麼辦呢?如果是jqgrid、easyUI的表格,tree

JS元件系列——Bootstrap Table 凍結列功能IE瀏覽器相容性問題解決方案

前言:最近專案裡面需要用到表格的凍結列功能,所謂“凍結列”,就是某些情況下表格的列比較多,需要固定前面的幾列,後面的列滾動。遺憾的是,bootstrap table裡自帶的fixed column功能有一點bug,於是和同事討論該如何解決,於是就有了這篇文章。 一、起因回顧 第一列固定 貌似

JS元件系列——又一款MVVM元件:Vue(二:構建自己的Vue元件

前言:轉眼距離上篇 JS元件系列——又一款MVVM元件:Vue(一:30分鐘搞定前端增刪改查) 已有好幾個月了,今天打算將它撿起來,發現好久不用,Vue相關技術點都生疏不少。經過這幾個月的時間,Vue的發展也是異常迅猛,不過這好像和博主都沒什麼太大的關係,博主還是老老實實研究自己的技術吧。技術之路還很長,且行

JS元件系列——分享自己封裝的Bootstrap樹形元件:jqTree

 前言:之前的一篇介紹了下如何封裝自己的元件,這篇再次來體驗下自己封裝元件的樂趣。看過博主部落格的園友應該記得之前分享過一篇樹形選單的使用JS元件系列——Bootstrap 樹控制元件使用經驗分享,這篇裡面第一個Jquery Tree,只是用簡單樣式和js去實現了效果,沒有給出一個系統的封裝,這篇博主就來試試

JS元件系列——封裝自己JS元件,你也可以

前言:之前分享了那麼多bootstrap元件的使用經驗,這篇博主打算研究下JS元件的擴充套件和封裝,我們來感受下JQuery為我們提供$.Extend的神奇,看看我們怎麼自定義自己的元件,比如我們想擴充套件一個$("#id").MyJsControl({})做我們自己的元件,我們該如何去做呢,別急,我們慢慢來

JS元件系列——使用HTML標籤的data屬性初始化JS元件

前言:最近使用bootstrap元件的時候發現一個易用性問題,很多簡單的元件初始化都需要在JS裡面寫很多的初始化程式碼,比如一個簡單的select標籤,因為僅僅只是需要從後臺獲取資料填充到option裡面,可是從後臺取資料就需要js的初始化,所以導致頁面初始化的時候js的初始化程式碼裡面出現很多重複

JS元件系列——兩種bootstrap multiselect元件大比拼[轉載]

轉載原文地址:https://www.cnblogs.com/landeanfen/p/5013452.html 前言:今天繼續來看看bootstrap的另一個元件:multiselect。記得在專案開始之前,博主專案組幾個同事就使用哪些js元件展開過討論,其中就說到了select元件,

JS元件系列——表格元件神器:bootstrap table(三:終結篇,最後的乾貨福利)

前言:前面介紹了兩篇關於bootstrap table的基礎用法,這章我們繼續來看看它比較常用的一些功能,來個終結篇吧,毛爺爺告訴我們做事要有始有終~~bootstrap table這東西要想所有功能覆蓋似乎不太現實,博主挑選了一些自認為比較常用的功能在此分享給各位園友。原始

JS元件系列——表格元件神器:bootstrap table

前言:之前一直在忙著各種什麼效果,殊不知最基礎的Bootstrap Table用法都沒有涉及,罪過,罪過。今天補起來吧。上午博主由零開始自己從頭到尾使用了一遍Bootstrap Table ,遇到不少使用方面的問題,也做了一部分筆記,在此分享出來供需要使用的園友參考。還記得前

JS元件系列——表格元件神器:bootstrap table(二:父子表和行列調序)

前言:上篇 JS元件系列——表格元件神器:bootstrap table 簡單介紹了下Bootstrap Table的基礎用法,沒想到討論還挺熱烈的。有園友在評論中提到了父子表的用法,今天就結合Bootstrap table的父子表和行列調序的用法再來介紹下它稍微高階點的用法

JS元件系列——又一款MVVM元件:Vue(一:30分鐘搞定前端增刪改查)

正文 前言:關於Vue框架,好幾個月之前就聽說過,瞭解一項新技術之後,總是處於觀望狀態,一直在猶豫要不要系統學習下。正好最近有點空,就去官網瞭解了下,看上去還不錯的一個元件,就抽空研究了下。最近園子裡vue也確實挺火,各種入門博文眼花繚亂,博主也不敢說寫

JS元件系列——BootstrapTable 行內編輯解決方案:x-editable

前言:之前介紹bootstrapTable元件的時候有提到它的行內編輯功能,只不過為了展示功能,將此一筆帶過了,罪過罪過!最近專案裡面還是打算將行內編輯用起來,於是再次研究了下x-editable元件,遇到過一些坑,再此做個採坑記錄吧!想要了解bootstrapTable的園友可以移步 JS元件系列——表格元

JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(一)

前言:出於某種原因,需要學習下Knockout.js,這個元件很早前聽說過,但一直沒嘗試使用,這兩天學習了下,覺得它真心不錯,雙向繫結的機制簡直太爽了。今天打算結合bootstrapTable和Knockout去實現一個簡單的增刪改查,來體驗一把神奇的MVVM。關於WebApi的剩餘部分,博主一定抽時間補上。

JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(二)

前言:上篇 JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(一) 介紹了下knockout.js的一些基礎用法,由於篇幅的關係,所以只能分成兩篇,望見諒!昨天就覺得應該快點完成下篇,要不然有點標題黨的感覺,思及此,博主心有不安,於是加班趕出了下篇。如果你也打算用ko去做

JS元件系列——不容錯過的兩款Bootstrap Icon圖示選擇元件

前言:最近好多朋友在群裡面聊到bootstrap icon圖示的問題,比如最常見的選單管理,每個選單肯定需要一個對應的選單圖示,要是有一個視覺化的圖示選擇元件就好了,最好是直接選擇圖示,就能得到對應的class樣式。於是乎各種百度,皇天不負有心人,最後被博主找到了,感覺效果還不錯,並且支援自定義的圖示,今天就

JS元件系列——圖片切換特效:簡易抽獎系統

<div id="randomize"> <div class="content container" style="text-align: center;max-width: 900px;"> <h1>簡易遊戲機</h

JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(三):兩個Viewmodel搞定增刪改查

前言:之前博主分享過knockoutJS和BootstrapTable的一些基礎用法,都是寫基礎應用,根本談不上封裝,僅僅是避免了html控制元件的取值和賦值,遠遠沒有將MVVM的精妙展現出來。最近專案打算正式將ko用起來,於是乎對ko和bootstraptable做了一些封裝,在此分享出來供園友們參考。封裝

JS元件系列——再推薦一款好用的bootstrap-select元件,親測還不錯

前言:之前分享過兩篇bootstrap下拉框的元件:JS元件系列——兩種bootstrap multiselect元件大比拼  和 JS元件系列——Bootstrap Select2元件使用小結 ,收到很多園友的關注和提問,最後總結這兩篇裡面的下拉框元件都存在一些大大小小的問題,比如兩種bootstrap m