This article introduces my recent implementation ideas for similar cascade functions in provincial and municipal cascades. In order to separate responsibilities, performance and behavior as much as possible, this function is split into 2 components and a single linked list is used to implement key cascade logic. The next section has a demonstration effect. Although this is a very common function, the implementation logic of this article is clear and the code is easy to understand. It is separated from the semantics of provincial and municipal cascades, and takes into account the separation of performance and behavior. I hope that the content of this article can bring some reference value to your work. Welcome to read and correct it.
Cascade Cascade Operation
CascadeType. PERSIST Cascade persistence (save) operation
CascadeType. MERGE Cascade Update (Mergy) Operation
CascadeType. REFRESH Cascade refresh operation, only query and get operations
CascadeType. REMOVE Cascade Delete Operation
CascadeType. ALL Cascade All operations above
Whether to delay loading of Fetch crawl, the default is that the first side is loading immediately, and the more side is delay loading
mappedBy relationship maintenance
mappedBy= "parentid" means that the parentid attribute in the children class is used to maintain the relationship. This name must be exactly the same as the parentid attribute name in the children class.
Also, it should be noted that the collection type in the parent class must be List or Set, and cannot be set to ArrayList, otherwise an error will be reported
Demonstration effect (code download, note: This effect requires http to run. In addition, the data in the effect is simulated data and is not actually returned by the background, so the pull-down data of provinces, cities and counties you see are the same):
Note: This article uses the technical implementation of the previous several related blogs. If you need it, you can click the link below to learn about it:
1) Detailed explanation of the inheritance implementation of Javascript: Provide a class.js to define the inheritance relationship between the javascript class and the construction class;
2) jquery skills to make any component support DOM-like event management: provide an eventBase.js to provide any component instance with DOM-like event management functions;
3) Secondary encapsulation of jquery's ajax and ajax cache proxy components: AjaxCache: provides ajax.js and ajaxCache.js, simplifies ajax calls of jquery, and cache proxy for client requests.
Let’s first learn more about the requirements of this function.
1. Functional Analysis
This function is illustrated by a cascading component containing three cascading items:
1) Each cascading item may require an option to be used as an input prompt:
In this case, an empty option can be selected in the data list of each cascading item (that is, the one prompted by the input):
It may also not be necessary to use as input prompts:
In this case, only data option can be selected in the data list of each cascading item, and no empty option can be selected:
2) If the current page is querying from the database and the fields corresponding to the cascading component have values, then the queryed value is echoed on the cascading component:
If the corresponding field found in the query has no value, then the two situations described in the requirements of point 1) are displayed.
3) Each cascading item forms a single linked list relationship in the data structure. The data list of the latter cascading item is related to the data selected by the previous cascading item.
4) Considering performance issues, the data list of each cascading item is displayed asynchronously by ajax.
5) After the initialization of the cascading component is completed, the list of the first cascading item is automatically loaded.
6) When the current cascading item changes, clear the data list of all the cascading items that are directly or indirectly associated. At the same time, if the value after the previous cascading item is not empty, the data list of the next cascading item that is directly associated with it will be automatically loaded. When clearing the data list of the cascading items, be careful: If the cascading items need to display the input prompt option, the option must be retained when clearing.
7) We must fully consider performance issues and avoid duplicate loading.
8) Considering the issue of form submission, when any cascading item of the cascading component changes, the value selected by the cascading component must be reflected in a hidden text field, so as to facilitate the submission of the cascading component's value to the background through the text field.
The function is roughly as above.
2. Implementation ideas
1) Data structure
What is different from other components is that it has some dependencies with the data in the background. The data structure I consider is better implemented:
{"id": 1,"text": "Beijing","code": 110000,"parentId": 0},{"id": 2,"text": "Hebei Province","code": 220000,"parentId": 0},{"id": 3,"text": "Henan Province","code": 330000,"parentId": 0}id is the unique identifier of data, and the association relationship between data is constructed through parentId. Text and code are all ordinary business fields. If we follow this data structure, it will be very simple to query the interface of the cascading data list:
//Check the list of the first cascade item /api/cascade?parentId=0//Check the list of the second cascade item based on the value selected by the first cascade item /api/cascade?parentId=1//Check the list of the third cascade item based on the value selected by the second cascade item /api/cascade?parentId=4
This structure is also easy to handle for the background. Although they are structurally a tree-shaped table structure, the queries are all single-layered, so they are easy to implement.
It can also be seen from the previous query demonstration that this structure can help us unify the interface and parameters of data query into one, which is a very convenient thing for component development. After we get this data structure from the background, we parse each data into an option, such as <option value="Beijing" data-param-value="1">Beijing</option>. This can not only complete the drop-down display of the data list, but also collect the selected value of the current cascade item through the function of the select form element. Finally, when the cascade item changes, we can also obtain the selected option, and use the value of the data-param-value stored on it as the parentId parameter to load the list of the next cascade item. This is also the idea of cascading component data query and parsing.
However, what needs to be considered here is the issue of flexibility. In actual projects, the data structures of cascading components may be defined according to a similar association relationship such as id parentId, but their fields are not necessarily called id parentId text code, and are likely to be other fields. In other words: when parsing the data into an option, the field used for the option text and value, and the value of the field used for the data-param-value attribute are uncertain; the parameter name parentId used when querying data cannot be dead. Sometimes if the backend staff writes the query interface first and uses another name, you cannot ask someone to change its parameter name, because it needs to be compiled and deployed, which is more troublesome than the front end; the value of parentId=0 cannot be fixed, because the parentid of the first layer of data in the actual project may be empty or -1. These things must be designed as options. On the one hand, the default value is provided, and the external setting is also reserved according to the actual situation. For example, in the final implementation of this article, this option is defined like this:
textField: 'text', //The field name in the returned data to be displayed in the <option> element
valueField: 'text', //The field name in the returned data to be set on the value of the <option> element
paramField: 'id', //When calling the data query interface, the field name corresponding to the data to be passed to the background
paramName: 'parentId', // When calling the data query interface, the parameter name of the data is passed after the url
defaultParam: '', //When querying the first cascade item, the value passed to the background is generally 0, '', or -1, etc., indicating that the data in the upper layer is to be queryed
2) HTML structure
According to Article 1 of the previous functional analysis, there are two types of initial html structures of cascading components:
<ul id="licenseLocation-view"><li><select><option value="">Please select a province</option></select></li><li><select><option value="">Please select a city</option></select></li><li><select><option value="">Please select a district and county</option></select></li></ul>
or
<ul id="companyLocation-view"><li><select></select></li><li><select></select></li><li><li><select></select></li></ul>
The only difference between these two structures is whether the option used as input prompts is configured. It should also be noted that if this empty option is needed, the value attribute must be set to empty, otherwise this empty option will submit the option prompt information to the background when submitting the form.
The most important thing about these two structures is the select element, which has nothing to do with ul and li. ul and li are used for the UI; the select element has no semantics, and there is no need to identify which province, which is a city, and which is a district or county. Functionally speaking, a select represents a cascading item. It doesn't matter where these selects are defined. We just need to tell the cascading component which select elements its cascading items are composed of. The only thing that needs to be told to the component is the sequence of these select elements, but this is usually controlled by the default order of elements in the html. This structure can help us separate the functions of components from behavior as much as possible.
3) Separation of responsibilities and the use of single linked lists
It can be seen from the previous part that if this cascade component is divided according to responsibilities, it can be divided into two core components, one is responsible for the management of the overall functions and internal cascade items (CascadeView), and the other is responsible for the functional implementation of the cascade items (CascadeItem). In addition, in order to more conveniently implement cascading logic, we only need to connect all cascading items through a linked list. Through the publish-subscribe mode, the latter cascading item subscribes to the message that the previous cascading item has changed; when the current cascading item changes, a message is posted to notify the subsequent cascading item to process the relevant logic; through the role of the linked list, this message may be passed until the last cascading item. If you describe it in a picture, it will be roughly like this:
All we need to do is to control the release and delivery of good news.
4) Form Submission
In order to conveniently submit the value of the cascading component to the background, the entire cascading component can be treated as a whole, and an onChanged event is provided to the outside, through which the external event can obtain the values of all cascading items. Since there are multiple cascades, when publishing the onChanged event, this event can only be triggered when any cascade changes.
5) ajax cache
In this component, we need to consider two levels of ajax cache. The first one is at the component level. For example, I switched the first cascade item to Beijing. At this time, the second cascade item loaded the data of Beijing. Then I switched the first cascade item from Beijing to Hebei and then to Beijing. At this time, the second cascade item still displays the associated data list of Beijing. If we cached its data when the list was first loaded, then we don’t need to initiate an ajax request this time; the second is ajax request. At the level, if there are multiple cascading components on the page, I first switch the first cascading item of the first cascading component to Beijing, and the browser initiates an ajax request to load data. When I switch the first cascading item of the second cascading component to Beijing, the browser will send another request to load data. If I cache the data returned by the first ajax request of the first component first, when the second component uses the same parameters to request the same interface, it will directly use the previous cache to return the result, which can also reduce the ajax request. The second level of ajax cache depends on the above "secondary encapsulation of jquery ajax and ajax cache proxy component: AjaxCache". For the component, it only implements the first level of cache internally, but it does not need to consider the second level of cache, because the cache implementation of the second level is transparent to it, and it does not know that the ajax component it uses has the function of cache.
3. Implementation details
The final implementation includes three components, CascadeView, CascadeItem, and CascadePublicDefaults. The first two are the core of the component, and the last one is just used to define some options. Its function is described in detail in the comments of CascadeItem. In addition, there are very detailed comments in the following code that explain the role of some key codes. Looking at the code based on the previous requirements, it should be relatively easy to understand. I used to use text to explain some implementation details, but later I gradually felt that this method was a bit thankless. First, the language at the details level was not easy to organize. Sometimes I didn’t express my meaning. I obviously wanted to explain something clearly, but it turned out to be even more confused. At least I would feel this way when I read what I wrote. Second, developers themselves have the ability to read source code, and most active developers are willing to understand implementation ideas by thinking about other people’s code; so I used annotation to explain implementation details instead:)
CascadePublicDefaults:
define(function () {return {url: '',//Data query interface textField: 'text', //Return data in the field name valueField: 'text', //Return data in the field name that should be displayed in the <option> element, paramField: 'text', //Return data in the field name that should be set on the value of the <option> element, paramField: 'id', //When calling the data query interface, the corresponding field name of the data to be passed to the background is paramName: 'parentId', //When calling the data query interface, the parameter name of the data passing after the url is defaultParam: '', //When querying the first cascade item, the value passed to the background is generally 0, '', or -1, etc., indicating that you want to query the upper-level data keepFirstOption: true, // Whether to keep the first option (used as an input prompt, such as: please select a province), if true, the default first optionresolveAjax will not be cleared when reloading the cascade item: function (res) {return res;}//Because the cascade item will send an asynchronous request when loading data, this callback is used to parse the response returned by the asynchronous request}});CascadeView:
define(function (require, exports, module) {var $ = require('jquery');var Class = require('mod/class');var EventBase = require('mod/eventBase');var PublicDefaults = require('mod/cascadePublicDefaults');var CascadeItem = require('mod/cascadeItem');/*** The function of PublicDefaults is shown in the comments in the CascadeItem component*/var DEFAULTS = $.extend({}, PublicDefaults, {$elements: undefined, //Array of cascaded item jq objects, the order of elements in the data represents the order of cascaded valueSeparator: ',', //The separator used to obtain the values of all cascaded items. If it is an English comma, the returned value is like Beijing City, District, Chaoyang District values: '', //The string separated by valueSeparator represents the value of each select at the beginning onChanged: $.noop //This event will be triggered when the value of any cascaded item changes}); var CascadeView = Class({instanceMembers: {init: function (options) {//Calling the init method of the parent class EventBase through this.base this.base(); var opts = this.options = this.getOptions(options),items = this.items = [],that = this,$elements = opts.$elements,values = opts.values.split(opts.valueSeparator);this.on('changed.cascadeView', $.proxy(opts.onChanged, this));$elements && $elements.each(function (i) {var $el = $(this);//Instantiate the CascadeItem component and point the prevItem property of each instance to the previous instance//Set the first prevItem property to undefinedvar cascadeItem = new CascadeItem($el, $.extend(that.getItemOptions(), {prevItem: i == 0 ? undefined : items[i - 1],value: $.trim(values[i])}));items.push(cascadeItem);//Each cascade instance changes will trigger the change event of the CascadeView component//Outsides can handle business logic in this callback/For example, set the values of all cascade items to a hidden field for form submission cascadeItem.on('changed.cascadeItem', function () {that.trigger('changed.cascadeView', that.getValue());});});});//Initialization completes automatically loading the first cascading items.length && items[0].load();},getOptions: function (options) {return $.extend({}, this.getDefaults(), options);},getDefaults: function () {return DEFAULTS;},getItemOptions: function () {var opts = {}, _options = this.options;for (var i in PublicDefaults) {if (PublicDefaults.hasOwnProperty(i) && i in _options) {opts[i] = _options[i];}} return opts;},//Get the values of all cascading items, which are a string separated by valueSeparator//The value of an empty cascading item will not return getValue: function () {var value = [];this.items.forEach(function (item) {var val = $.trim(item.getValue());val != '' && value.push(val);});return value.join(this.options.valueSeparator);}}, extend: EventBase});return CascadeView;});CascadeItem:
define(function (require, exports, module) {var $ = require('jquery');var Class = require('mod/class');var EventBase = require('mod/eventBase');var PublicDefaults = require('mod/cascadePublicDefaults');var AjaxCache = require('mod/ajaxCache');//This is a cacheable Ajax component var Ajax = new AjaxCache();/*** There is a part of the option defined in PublicDefaults, because the CascadeItem component will not be used directly by external * The CascadeView component is used externally, so some options must become public. When the CascadeView component is also defined once * All options are passed through the CascadeView component* When the CascadeView internal instantiation of the CascadeItem, then the option in PublicDefaults is passed to CascadeItem*/var DEFAULTS = $.extend({}, PublicDefaults, {prevItem: undefined, // Point to the previous cascade item value: '' // value displayed at the beginning});var CascadeItem = Class({instanceMembers: {init: function ($el, options) {//Calling the init method of the parent class EventBase through this.base; this.$el = $el; this.options = this.getOptions(options); this.prevItem = this.options.prevItem; //The previous cascade item this.hasContent = false;//This variable is used to control whether the data needs to be reloaded this.cache = {};//Use to cache data var that = this;//The change event $el.on('change', function () of proxying the select element {that.trigger('changed.cascadeItem');});// When the value of a cascade item changes, the processing is to clear and reload the data as needed. This.prevItem && this.prevItem.on('changed.cascadeItem', function () {//As long as the value of the previous one changes and there is content itself, the content must be cleared that.hasContent && that.clear();//If it is not the first cascade item and the previous cascade item does not select a valid option, it will not be processed if (that.prevItem && $.trim(that.prevItem.getValue()) == '') return;that.load();});var value = $.trim(this.options.value);value !== '' && this.one('render.cascadeItem', function () {//Set the initial value that.$el.val(value.split(','));//Notify the subsequent cascade to clean and reload the data that.trigger('changed.cascadeItem');});},getOptions: function (options) {return $.extend({}, this.getDefaults(), options);},getDefaults: function () {return DEFAULTS;},clear: function () {var $el = this.$el;$el.val('');if (this.options.keepFirstOption) {//Keep the first option$el.children().filter(':gt(0)').remove();} else {//Clear all $el.html('');}//Notify the subsequent cascade items to clear and reload the data this.trigger('changed.cascadeItem'); this.hasContent = false;//Indicate that the content is empty},load: function () {var opts = this.options,paramValue, that = this,dataKey;//dataKey is the key name used when cache cache // Since the data of the first cascading item is the top-level data, a fixed and unique key is used when cached: root//The key name used when cached by other cascading items is related to the option selected by the previous selection if (!this.prevItem) {paramValue = opts.defaultParam;dataKey = 'root';} else {paramValue = this.prevItem.getParamValue();dataKey = paramValue;}//First check whether there is loaded data in the data cache, and if there is, it will be displayed directly to avoid Ajaxif (dataKey in this.cache) {this.render(this.cache[dataKey]);} else {var params = {};params[opts.paramName] = paramValue;Ajax.get(opts.url, params).done(function (res) {//resolveAjax This callback is used to parse the data returned by ajax externally//It needs to return a data array var data = opts.resolveAjax(res);if (data) {that.cache[dataKey] = data;that.render(data);}});}},reender: function (data) {var html = [],opts = this.options;data.forEach(function (item) {html.push(['<option value="',item[opts.valueField],'" data-param-value="',//Storage the corresponding value of paramField on the data-param-value property of option item[opts.paramField],'">',item[opts.textField],'</option>'].join(''));});//Add dynamically in the form of append to avoid affecting the first option//In the end, set the value to empty this.$el.append(html.join('')).val(''); this.hasContent = true;// means that there is content this.trigger('render.cascadeItem');},getValue: function () {return this.$el.val();},getParamValue: function () {return this.$el.find('option:selected').data('paramValue');}}, extend: EventBase}); return CascadeItem;});4. Demo Instructions
Demonstrate the structure of the code:
What is framed is the relevant part of the demonstration. html/regist.html is the page that demonstrates the effect, and js/app/regist.js is the entry to the demonstration effect js:
define(function (require, exports, module) { var $ = require('jquery'); var CascadeView = require('mod/cascadeView'); function publicSetCascadeView(fieldName, opts) { this.cascadeView = new CascadeView({ $elements: $('#' + fieldName + '-view').find('select'), url: '../api/cascade.json', onChanged: this.onChanged, values: opts.values, keepFirstOption: this.keepFirstOption, resolveAjax: function (res) { if (res.code == 200) { return res.data; } } } }); } var LOCATION_VIEWS = { licenseLocation: { $input: $('input[name="licenseLocation"]'), keepFirstOption: true, setCascadeView: publicSetCascadeView, onChanged: function(e, value){ LOCATION_VIEWS.licenseLocation.$input.val(value); } }, companyLocation: { $input: $('input[name="companyLocation"]'), keepFirstOption: false, setCascadeView: publicSetCascadeView, onChanged: function(e, value){ LOCATION_VIEWS.companyLocation.$input.val(value); } } } }; LOCATION_VIEWS.licenseLocation.setCascadeView('licenseLocation', { values: LOCATION_VIEWS.licenseLocation.$input.val() }); LOCATION_VIEWS.companyLocation.setCascadeView('companyLocation', { values: LOCATION_VIEWS.companyLocation.$input.val() });});Pay attention to the function of the variable LOCATION_VIEWS in the above code, because there are multiple cascading components on the page. This variable is actually managed in a similar way through the policy model. If you don't do this, it is easy to generate duplicate code; this form is also more conducive to the separation and encapsulation of some business logic in the entry file, such as the place where business logic is processed.
5. others
This is probably the last blog written by the company now. You have to go to work in a new unit in two days. I am not sure if you have so much spare time to record your usual work ideas, but at least you have developed the habit of writing blogs, and you will squeeze out time if you don’t have time in the future. The goal this year is mainly to broaden the knowledge and improve the quality of the code. The subsequent blogs will be more in the category of component development. I hope that you can continue to pay attention to the Wulin.com website in the future!