AnonSec Shell
Server IP : 85.193.89.191  /  Your IP : 3.131.37.56
Web Server : Apache
System : Linux 956367-cx40159.tmweb.ru 3.10.0-1160.105.1.el7.x86_64 #1 SMP Thu Dec 7 15:39:45 UTC 2023 x86_64
User : bitrix ( 600)
PHP Version : 8.1.27
Disable Function : NONE
MySQL : OFF  |  cURL : OFF  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/bitrix/www/bitrix/js/sale/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     

Current File : /home/bitrix/www/bitrix/js/sale//core_ui_chainedselectors.js
BX.ui.chainedSelectors = function(opts, nf){

	this.parentConstruct(BX.ui.chainedSelectors, opts);

	/*
	events:
		parent:
			*
		BX.ui.chainedSelectors:
			control-change
	*/

	BX.merge(this, {
		opts: { // default options
			source:						'/somewhere.php', // url that will be used to obtain select options from
			paginatedRequest:			false,

			// behaviour
			autoSelectWhenSingle:		true,

			knownBundles:				{}, // tree levels that already known
			selectedItem:				false, // initially selected path in a tree
			initialBundlesIncomplete:	false, // treat knownBundles as incomplete and try to re-get from server at the first suitable moment
			bundlesIncomplete: 			{}, // lists exactly which bundles in knownBundles are incomplete
			rootNodeValue:				0,
			ignoreUnSelectable:			false,

			adapterName:				'combobox',

			messages: {
				nothingFound:	'Sorry, nothing found',
				notSelected:	'-- Not selected',
				error:			'Error occured'
			},

			bindEvents: {
				init: function(){ // after all we do this
					this.setInitialValue();
					this.vars.allowHideErrors = true;
				}
			}
		},
		vars: { // significant variables
			stack: [], // sequence of controls
			cache: { // tree level cache
				links: {}, // relations of parent-children kind
				nodes: {}, // node data
				incomplete: {} // array of indicators of incomplete links (links that should be refreshed and completed before passing to control)
			},
			/*
			keys in each node:
			DISPLAY, VALUE, IS_PARENT, CAN_CHOOSE
			*/
			value: 					false, // currently selected value
			eventLock: 				false,
			allowHideErrors: 		false
		},
		sys: {
			code: 'chainedselectors'
		}
	});

	this.handleInitStack(nf, BX.ui.chainedSelectors, opts);
}
BX.extend(BX.ui.chainedSelectors, BX.ui.networkIOWidget);

// the following functions can be overrided with inheritance
BX.merge(BX.ui.chainedSelectors.prototype, {

	// member of stack of initializers, must be defined even if does nothing
	init: function(){

		var so = this.opts,
			sc = this.ctrls,
			sv = this.vars;

		sc.targetInput = this.getControl('target');
		sc.errorMessage = this.getControl('error', true);

		//sc.loader = new BX.ui.loader({scope: this.getControl('loader'), timeout: 500});

		sc.pool = this.getControl('pool');
		if(typeof sc.pool == 'undefined')
			sc.pool = sc.scope;

		if(typeof so.knownBundles == 'object'){
			this.fillCache(so.knownBundles);

			if(so.initialBundlesIncomplete){
				for(var k in so.knownBundles)
					if(so.knownBundles.hasOwnProperty(k))
						sv.cache.incomplete[k] = true;
			}else if(typeof so.bundlesIncomplete != 'undefined'){

				for(var k in so.knownBundles)
				{
					if(so.knownBundles.hasOwnProperty(k))
						if(typeof so.bundlesIncomplete[k] != 'undefined')
							sv.cache.incomplete[k] = true;
				}
			}
		}

		this.pushFuncStack('buildUpDOM', BX.ui.chainedSelectors);
		this.pushFuncStack('bindEvents', BX.ui.chainedSelectors);
	},

	buildUpDOM: function(){},

	bindEvents: function(){

		var ctx = this,
			so = this.opts,
			sv = this.vars,
			sc = this.ctrls;

		this.bindEvent('control-change', this.controlChangeActions);

		BX.bind(sc.targetInput, 'change', function(){

			if(sv.eventLock)
				return;

			ctx.setValue(this.value);
		});
	},

	////////// PUBLIC: free to use outside

	// todo
	addItems2Cache: function(){
	},

	// todo
	clearCache: function(){
	},

	// todo
	focus: function(){
	},
	
	// todo
	checkDisabled: function(){
	},

	// todo
	disable: function(){
	},

	// todo
	enable: function(){
	},

	setValue: function(value){

		var sv = this.vars;

		// same value
		if(sv.value != false && typeof value != 'undefined' && value == sv.value)
			return;

		if(value == null || value == false || typeof value == 'undefined' || value.toString().length == 0){ // deselect
			this.displayRoute([]);
			this.setValueVariable('');
			this.setTargetValue('');
			this.fireEvent('after-clear-selection');
			return;
		}

		// set
		this.fireEvent('before-set-value', [value]);

		var d = new BX.deferred();
		var ctx = this;

		d.done(BX.proxy(function(route){

			this.displayRoute(route);
			sv.value = value;
			this.setTargetValue(this.checkCanSelectItem(value) ? value : this.getLastValidValue());

		}, this));

		d.fail(function(type){
			if(type == 'notfound'){

				ctx.displayRoute([]);
				ctx.setValueVariable('');
				ctx.setTargetValue('');
				ctx.showError({errors: [ctx.opts.messages.nothingFound], type: 'server-logic', options: {}});
			}
		});

		this.hideError();

		this.getRouteToNode(value, d);
	},

	getValue: function(){
		return this.vars.value === false ? '' : this.vars.value;
	},

	clearSelected: function(){
		this.setValue('');
	},

	getNodeByValue: function(value){
		return this.vars.cache.nodes[value];
	},

	// todo
	setTabIndex: function(index){
	},

	setTargetInputName: function(newName){
		this.ctrls.targetInput.setAttribute('name', newName);
	},

	// todo:
	cancelRequest: function(){
	},

	// specific

	getStackSize: function(){
		return this.vars.stack.length;
	},

	getAdapterAtPosition: function(pos){
		if(pos < 0 || pos >= this.vars.stack.length)
			return null;

		return this.vars.stack[pos].control;
	},

	// low-level, use with caution

	setTargetInputValue: function(value){

		this.vars.eventLock = true;
		this.ctrls.targetInput.value = value;
		BX.fireEvent(this.ctrls.targetInput, 'change');
		this.vars.eventLock = false;
	},

	// todo
	setFakeInputValue: function(display){
	},

	setValueVariable: function(value){
		this.vars.value = value;
	},

	////////// PRIVATE: forbidden to use outside (for compatibility reasons)

	checkCanSelectItem: function(itemId){

		if(this.opts.ignoreUnSelectable)
			return true;

		var nodes = this.vars.cache.nodes;

		if(typeof nodes[itemId] == 'undefined')
			return false;

		if(typeof nodes[itemId].IS_UNCHOOSABLE == 'undefined')
			return true;

		return !nodes[itemId].IS_UNCHOOSABLE;
	},

	controlChangeActions: function(stackIndex, value){

		var ctx = this,
			so = this.opts,
			sv = this.vars,
			sc = this.ctrls;

		this.hideError();

		////////////////

		// todo: replace code below with a single call of ctx.setValue() with no intention to drop entire stack, but just append missing items

		if(value.length == 0){

			ctx.truncateStack(stackIndex);
			sv.value = ctx.getLastValidValue();
			ctx.setTargetValue(sv.value);

		}else{

			var node = sv.cache.nodes[value];

			if(typeof node == 'undefined')
				throw new Error('Selected node not found in the cache');

			// node found

			ctx.truncateStack(stackIndex);

			// links will be downloaded on-demand
			if(typeof sv.cache.links[value] != 'undefined' || node.IS_PARENT)
				ctx.appendControl(value);

			if(ctx.checkCanSelectItem(value)){
				sv.value = value;
				ctx.setTargetValue(value);
			}

			/*
			if(typeof sv.cache.links[value] != 'undefined'){

				// links exist
				ctx.appendControl(value);
				ctx.setNextValidValue(node, value);

			}else{

				if(node.IS_PARENT){

					var d = new BX.deferred();
					d.done(function(){
						ctx.appendControl(value);
						ctx.setNextValidValue(node, value);
					});
					d.fail(function(){
						ctx.showError({errors: [so.messages.nothingFound], type: 'server-logic', options: {}});
					});

					ctx.getBundleForNode(value, d);
				}else
					ctx.setTargetValue(value);

			}
			*/
		}
	},

	// for changing targerInput value, use this function only
	setTargetValue: function(value){
		this.setTargetInputValue(value);
		this.fireEvent('after-select-item', [value]);
	},

	// try to get route from cache, if any
	getRouteToNodeFromCache: function(value){
		var route = [],
			sv = this.vars;

		if(typeof sv.cache.nodes[value] == 'undefined')
			return route;

		var node = sv.cache.nodes[value];
		route.unshift(node.VALUE);

		var i = 0;
		var rootNodeFound = false;
		var limit = BX.util.getObjectLength(sv.cache.nodes);
		while(i < limit){

			var parent = node.PARENT_VALUE;

			if(parent == this.opts.rootNodeValue)
				rootNodeFound = true;

			route.unshift(parent);

			if(typeof parent == 'undefined' || parent == this.opts.rootNodeValue/*'0'*/ || parent == '0' /*also allowed as root node*/){
				break;
			}else{

				if(typeof sv.cache.nodes[parent] == 'undefined')
					throw new Error('Tree integrity compromised');

				node = sv.cache.nodes[parent];
			}

			i++;
		}

		return route;
	},

	setInitialValue: function(){

		var initalValue = false;
		if(this.opts.selectedItem !== false)
			initalValue = this.opts.selectedItem;
		else if(this.ctrls.targetInput.value.length > 0)
			initalValue = this.ctrls.targetInput.value;

		this.setValue(initalValue);
	},

	// get route for nodeId and resolve deferred with it
	getRouteToNode: function(nodeId, d){
		var sv = this.vars,
			ctx = this;

		if(typeof nodeId != 'undefined' && nodeId !== false && nodeId.toString().length > 0){

			var route = this.getRouteToNodeFromCache(nodeId);

			if(route.length == 0){ // || (sv.cache.nodes[nodeId].IS_PARENT && typeof sv.cache.links[nodeId] == 'undefined')){

				// no way existed or item is parent without children downloaded

				// download route, then try again
				ctx.downloadBundle({
					request: {VALUE: nodeId}, // get only route
					callbacks: {
						onLoad: function(data){

							// mark absent as incomplete, kz we do not know if there are really more items of that level or not
							for(var k in data)
							{
								if(data.hasOwnProperty(k))
									if(typeof sv.cache.links[k] == 'undefined')
										sv.cache.incomplete[k] = true;
							}

							ctx.fillCache(data, true);

							var route = ctx.getRouteToNodeFromCache(nodeId); // trying to re-get

							if(route.length == 0)
								d.reject('notfound');
							else
								d.resolve(route);
						},
						onError: function(){ // this will only trigger on internal error, not server-logic error
							d.reject('internal');
						}
					},
					options: {} // accessible in refineRequest\refineResponce and showError
				});

			}else
				d.resolve(route);
		}else
			d.resolve([]);
	},

	// get bundle for nodeId and resolve deferred with it
	getBundleForNode: function(nodeId, d){
		var sv = this.vars,
			ctx = this;

		if(typeof nodeId != 'undefined' && nodeId !== false && nodeId.toString().length > 0){

			if(typeof this.vars.cache.links[nodeId] == 'undefined' || this.vars.cache.incomplete[nodeId] === true){

				// no way existed or item is parent without children downloaded

				// download bundle, then try again
				ctx.downloadBundle({
					request: {PARENT_VALUE: nodeId}, // get children
					callbacks: {
						onLoad: function(data){

							ctx.fillCache(data);

							if(typeof this.vars.cache.links[nodeId] == 'undefined'){ // still not found
								d.reject();
							}else{
								delete(sv.cache.incomplete[nodeId]);
								d.resolve(this.vars.cache.links[nodeId]);
							}
						},
						onError: function(){
							d.reject();
						}
					},
					options: {} // accessible in refineRequest\refineResponce and showError
				});

			}else
				d.resolve(this.vars.cache.links[nodeId]);
		}else
			d.resolve([]);
	},

	// print route that have been found
	displayRoute: function(route){
		var sv = this.vars;

		this.truncateStack(-1); // drop entire stack

		if(route.length == 0)
			this.appendControl(this.opts.rootNodeValue/*0*/);
		else{
			route = BX.clone(route);

			//route.unshift(this.opts.rootNodeValue/*'0'*/);

			for(var k = 0; k < route.length; k++){
				var nodeId = route[k];

				if(typeof sv.cache.links[nodeId] != 'undefined' || sv.cache.nodes[nodeId].IS_PARENT){
					this.appendControl(nodeId, route[k+1]);

					/*
					// disabling control where is only one option
					if(typeof sv.cache.nodes[nodeId] != 'undefined' && sv.cache.nodes[nodeId].IS_PARENT && sv.cache.links[nodeId].length == 1 && sv.cache.incomplete[nodeId] !== true){

						var control = this.getLastControl().control;

						BX.adjust(control, {
							attrs: {disabled: 'disabled'}
						});
					}
					*/
				}
			}
		}
	},

	appendControl: function(parentNode, selectedNode){

		var params = {
			parent: this,
			messages: this.opts.messages
		};

		if(typeof selectedNode != 'undefined')
			params.selectedItem = selectedNode;

		params.parentNode = parentNode;

		if(typeof this.vars.cache.links[parentNode] != 'undefined'){

			params.knownItems = [];
			for(var k in this.vars.cache.links[parentNode])
				if(this.vars.cache.links[parentNode].hasOwnProperty(k))
					params.knownItems.push(this.vars.cache.nodes[this.vars.cache.links[parentNode][k]]);
		}

		var control = new BX.ui.chainedSelectors.adapters[this.opts.adapterName](params);

		control.setIndex(this.vars.stack.length);
		this.vars.stack.push({control: control});
	},

	setNextValidValue: function(node, value){

		var	sv = this.vars,
			so = this.opts,
			sc = this.ctrls;

		var hasLinks = typeof sv.cache.links[value] != 'undefined';
		var linkCnt = sv.cache.links[value].length;

		if(so.autoSelectWhenSingle && node.IS_PARENT && hasLinks && linkCnt == 1){

			// do one step farther, user is no needed for clicking
			var control = this.getLastControl().control;
			/*
			BX.adjust(control, {
				attrs: {disabled: 'disabled'}
			});
			*/

			control.value = sv.cache.links[value][0];
			BX.fireEvent(control, 'change');
		}else{

			this.setTargetValue(node.IS_UNCHOOSABLE ? this.getLastValidValue() : value);
		}
	},

	getLastValidValue: function(){

		var value = undefined,
			sc = this.ctrls,
			sv = this.vars;

		for(var k = sv.stack.length - 1; k >= 0 ; k--){
			value = sv.stack[k].control.getValue();

			if(typeof value != 'undefined' && (typeof sv.cache.nodes[value] != 'undefined') && (typeof sv.cache.nodes[value].IS_UNCHOOSABLE == 'undefined'))
				return value;
		}

		return '';
	},

	getLastControl: function(){
		return this.vars.stack[this.vars.stack.length - 1];
	},

	truncateStack: function(pos){

		if(pos < -1)
			return;

		var len = this.vars.stack.length;
		for(var k = pos + 1; k < len; k++){
			this.vars.stack[k].control.remove();

		}

		this.vars.stack.splice(pos + 1, len - (pos + 1));
	},

	getSelectorValue: function(value){
		return value;
	},

	fillCache: function(levels, isIncompleteData){

		var sv = this.vars,
			so = this.opts;

		for(var parent in levels)
		{
			if(!levels.hasOwnProperty(parent))
				continue;

			// overwrite if not set
			if(typeof sv.cache.links[parent] == 'undefined' || sv.cache.incomplete[parent] === true){

				// if not set or complete data passed - recreate
				if(typeof sv.cache.links[parent] == 'undefined' || !isIncompleteData)
					sv.cache.links[parent] = [];

				for(var k in levels[parent])
				{
					if(!levels[parent].hasOwnProperty(k))
						continue;

					sv.cache.links[parent].push(levels[parent][k].VALUE);
					levels[parent][k].PARENT_VALUE = parent;

					this.addItem2Cache(levels[parent][k]);
				}
			}
		}
	},

	addItem2Cache: function(item){
		this.vars.cache.nodes[item.VALUE] = item;
	},

	getCurrentItem: function(){
		return this.vars.cache.nodes[this.vars.value];
	},

	setTargetInputName: function(newName){
		this.ctrls.targetInput.setAttribute('name', newName);
	},

	showError: function(parameters){

		this.setCSSState('error', this.ctrls.scope);

		if(BX.type.isElementNode(this.ctrls.errorMessage)){
			this.ctrls.errorMessage.innerHTML = BX.util.htmlspecialchars(parameters.errors.join(', '));
			BX.show(this.ctrls.errorMessage);
		}

		BX.debug(parameters);
	},

	hideError: function(){

		if(this.vars.allowHideErrors)
		{
			this.dropCSSState('error', this.ctrls.scope);

			if(BX.type.isElementNode(this.ctrls.errorMessage))
				BX.hide(this.ctrls.errorMessage);
		}
	}

	/* Behaviour functions below */
});

BX.ui.chainedSelectors.adapters = {};

// this represents one (single) control that can be used as selector
BX.ui.chainedSelectors.adapters.combobox = function(options){

	this.opts = options;
	this.control = null;
	this.scope = null;
	this.index = null;

	this.place = function(){

		var ctx = this;

		var nodes = this.opts.parent.createNodesByTemplate('selector-scope', {}, true);
		if(nodes == null)
			return;

		this.scope = nodes[0];
		BX.append(this.scope, this.opts.parent.ctrls.pool);

		options.scope = this.scope;
		options.arrowScrollAdditional = 5;
		options.focusOnMouseSelect = false;

		this.opts.parent.fireEvent('before-control-placed', [this]);

		this.control = new BX.ui.combobox(options);
		this.control.vars.outSideClickScope = this.scope; // a little makeup-related spike to make combobox dropdown feel better

		this.control.bindEvent('item-list-discover', BX.proxy(function(d){ // when control doesnt know about nodes, it asks for it

			d.stopRace(); // drop auto-reject by timeout in deferred

			var dBundle = new BX.deferred();
			dBundle.done(BX.proxy(function(bundle){

				// convert here from links to required format
				// could be slow here
				var knownItems = [];
				for(var k in bundle)
					if(bundle.hasOwnProperty(k))
						knownItems.push(this.opts.parent.vars.cache.nodes[bundle[k]]);

				this.opts.parent.fireEvent('before-control-item-discover-done', [knownItems, this]);

				d.resolve({items: knownItems});

			}, this));

			this.opts.parent.getBundleForNode(this.opts.parentNode, dBundle);
		}, this));

		// transfer events from inner widget to outer... bad solution...
		this.control.bindEvent('before-display-page', function(){
			ctx.opts.parent.fireEvent('control-before-display-page', [ctx]);
		});

		this.control.bindEvent('after-select-item', BX.proxy(function(value){
			this.opts.parent.fireEvent('control-change', [this.index, value]);
		}, this));

		this.control.bindEvent('after-deselect-item', BX.proxy(function(){
			this.opts.parent.fireEvent('control-change', [this.index, '']);
		}, this));

		if(this.opts.parent.vars.cache.incomplete[this.opts.parentNode])
			this.control.clearCache();

		this.opts.parent.fireEvent('after-control-placed', [this]);
	};
	this.remove = function(){

		if(this.control != null)
		{
			this.control.remove();
		}

		this.control = null;

		this.opts = null;
		BX.remove(this.scope);

		this.scope = null;
	};
	this.setIndex = function(index){
		this.index = index;
	};
	this.getIndex = function(){
		return this.index;
	};
	this.getValue = function(){
		return this.control.getValue();
	};
	this.getParentValue = function(){
		return this.opts.parentNode;
	};
	this.getControl = function(){
		return this.control;
	};

	// a little hacky method, avoid to use it
	// its purpose is to force control to display information we need without any internal logic evaluation
	this.setValuePair = function(value, display){
		this.control.setTargetInputValue(value);
		this.control.setFakeInputValue(display);
		this.control.setValueVariable(value);
	};

	this.place();

	return this;
}

Anon7 - 2022
AnonSec Team