Your IP : 3.133.130.105


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/landing/install/js/landing/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/landing/install/js/landing/block.js

;(function() {
	"use strict";

	BX.namespace("BX.Landing");

	function getTypeSettings(prop)
	{
		var lp = BX.Landing.Main.getInstance();
		return lp.styleRepository["bitrix"]["style"][prop];
	}

	function isGroup(prop)
	{
		var lp = BX.Landing.Main.getInstance();
		return prop in lp.styleRepository["bitrix"]["group"];
	}

	function getGroupTypes(group)
	{
		var lp = BX.Landing.Main.getInstance();
		return lp.styleRepository["bitrix"]["group"][group];
	}

	/**
	 * Implements interface for works with landing block
	 *
	 * @param {HTMLElement} element
	 * @param {blockOptions} options
	 *
	 * @property {BX.Landing.UI.Collection.PanelCollection.<BX.Landing.UI.Panel.BasePanel>} panels - Panels collection
	 * @property {BX.Landing.Collection.CardCollection.<BX.Landing.Block.Card>} cards - Cards collection
	 * @property {BX.Landing.Collection.NodeCollection.<BX.Landing.Block.Node>} nodes - Nodes collection
	 * @property {blockManifest} manifest
	 *
	 * @constructor
	 */
	BX.Landing.Block = function(element, options)
	{
		this.node = element;
		this.id = typeof options.id === "number" ? options.id : 0;
		this.selector = "#block" + (typeof options.id === "number" ? options.id : 0) + " > :first-child";
		this.active = typeof options.active === "boolean" ? options.active : true;
		this.manifest = typeof options.manifest === "object" ? options.manifest : {};
		this.manifest.nodes = typeof options.manifest.nodes === "object" ? options.manifest.nodes : {};
		this.manifest.cards = typeof options.manifest.cards === "object" ? options.manifest.cards : {};
		this.onStyleInputWithDebounce = BX.debounce(this.onStyleInput, 1000, this);
		this.changeTimeout = null;
		this.changedNodes = new BX.Landing.Collection.BaseCollection();
		this.styles = new BX.Landing.Collection.BaseCollection();
		this.forms = new BX.Landing.UI.Collection.FormCollection();

		// Make entities collections
		this.nodes = new BX.Landing.Collection.NodeCollection();
		this.cards = new BX.Landing.Collection.CardCollection();
		this.panels = new BX.Landing.UI.Collection.PanelCollection();

		// Make lazy initialization by mousemove on block node
		this.onMouseMove = this.onMouseMove.bind(this);
		this.node.addEventListener("mousemove", this.onMouseMove);

		// Make read only objects
		Object.freeze(this.manifest);
		Object.freeze(this.manifest.nodes);
		Object.freeze(this.manifest.cards);

		// Apply block state
		this.node.classList[this.active ? "remove" : "add"]("landing-block-disabled");

		// Sets state
		this.state = "ready";

		// Init panels
		this.initPanels();

		// Fire block init event
		var event = this.createEvent();
		BX.onCustomEvent(window, "BX.Landing.Block:init", [event]);
	};


	BX.Landing.Block.prototype = {
		/**
		 * Handles mouse move event on block node
		 * Implements lazy initialization entities of block
		 * @private
		 */
		onMouseMove: function()
		{
			if (this.state === "ready")
			{
				this.node.removeEventListener("mousemove", this.onMouseMove);
				this.initEntities();
				this.lazyInitPanels();
				this.initStyles();
				this.state = "complete";
			}
		},


		/**
		 * Forces block initialization
		 */
		forceInit: function()
		{
			this.onMouseMove();
		},


		/**
		 * Creates block event object
		 * @param {object} [options]
		 * @returns {BX.Landing.Event.Block}
		 */
		createEvent: function(options)
		{
			return new BX.Landing.Event.Block({
				block: this.node,
				node: !!options && !!options.node ? options.node : null,
				card: !!options && !!options.card ? options.card : null,
				onForceInit: this.forceInit.bind(this)
			});
		},


		/**
		 * Initializes block entities
		 * @private
		 */
		initEntities: function()
		{
			this.initCards();
			this.initNodes();
			this.initStyles();
		},


		initPanels: function()
		{
			// Make "add block after this block" button
			if (!this.panels.get("create_action"))
			{
				var createPanel = new BX.Landing.UI.Panel.BaseButtonPanel(
					"create_action",
					"landing-ui-panel-create-action"
				);

				createPanel.addButton(new BX.Landing.UI.Button.Plus("insert_after", {
					text: BX.message("ACTION_BUTTON_CREATE"),
					onClick: this.addBlockAfterThis.bind(this)
				}));

				createPanel.show();
				this.addPanel(createPanel);
			}
		},


		/**
		 * Initializes action panels of block
		 * @private
		 */
		lazyInitPanels: function()
		{
			// Make content actions panel
			if (!this.panels.contains("content_actions"))
			{
				var contentPanel = new BX.Landing.UI.Panel.BaseButtonPanel(
					"content_actions",
					"landing-ui-panel-content-action"
				);

				if (typeof this.manifest.nodes !== "undefined")
				{
					contentPanel.addButton(new BX.Landing.UI.Button.Action("content", {
						text: BX.message("ACTION_BUTTON_CONTENT"),
						onClick: this.onShowContentPanel.bind(this)
					}));
				}

				if (typeof this.manifest.style !== "undefined")
				{
					contentPanel.addButton(new BX.Landing.UI.Button.Action("style", {
						text: BX.message("ACTION_BUTTON_STYLE"),
						onClick: this.onStyleShow.bind(this)
					}));
				}

				contentPanel.show();
				this.addPanel(contentPanel);
			}


			// Make block actions panel
			if (!this.panels.get("block_action"))
			{
				var blockPanel = new BX.Landing.UI.Panel.BaseButtonPanel(
					"block_action",
					"landing-ui-panel-block-action"
				);

				blockPanel.addButton(new BX.Landing.UI.Button.Action("down", {
					html: BX.message("ACTION_BUTTON_DOWN"),
					onClick: this.moveDown.bind(this)
				}));

				blockPanel.addButton(new BX.Landing.UI.Button.Action("up", {
					html: BX.message("ACTION_BUTTON_UP"),
					onClick: this.moveUp.bind(this)
				}));

				blockPanel.addButton(new BX.Landing.UI.Button.Action("actions", {
					html: BX.message("ACTION_BUTTON_ACTIONS"),
					onClick: this.showBlockActionsMenu.bind(this)
				}));

				blockPanel.addButton(new BX.Landing.UI.Button.Action("remove", {
					html: BX.message("ACTION_BUTTON_REMOVE"),
					onClick: this.deleteBlock.bind(this)
				}));

				blockPanel.show();
				this.addPanel(blockPanel);
			}
		},


		showBlockActionsMenu: function()
		{
			this.panels.get("block_action").buttons.get("actions").layout.classList.add("landing-ui-active");

			if (!this.blockActionsMenu)
			{
				this.blockActionsMenu = BX.PopupMenu.create(
					"block_"+this.id+"_actions",
					this.panels.get("block_action").buttons.get("actions").layout,
					[
						{
							text: BX.message("ACTION_BUTTON_ACTIONS_SHOW_HIDE"),
							onclick: function() {
								this.onStateChange();
								this.blockActionsMenu.close();
							}.bind(this)
						},
						{
							text: BX.message("ACTION_BUTTON_ACTIONS_CUT"),
							onclick: function() {
								this.blockActionsMenu.close();
							}.bind(this)
						},
						{
							text: BX.message("ACTION_BUTTON_ACTIONS_COPY"),
							onclick: function() {
								this.blockActionsMenu.close();
							}.bind(this)
						},
						{
							text: BX.message("ACTION_BUTTON_ACTIONS_PASTE"),
							onclick: function() {
								this.blockActionsMenu.close();
							}.bind(this)
						}
					],
					{
						angle: {
							position: "top",
							offset: 120
						},
						offsetTop: -6,
						events: {
							onPopupClose: function() {
								this.panels.get("block_action").buttons.get("actions").layout.classList.remove("landing-ui-active");
							}.bind(this)
						}
					}
				);

				this.blockActionsMenu.popupWindow.popupContainer.classList.add("landing-ui-block-actions-popup");
			}

			this.blockActionsMenu.show();
		},


		/**
		 * Moves block up
		 */
		moveUp: function()
		{
			var prev = BX.findPreviousSibling(this.node, {className: "block-wrapper"});

			if (prev)
			{
				var prevRect = prev.getBoundingClientRect();
				var currentRect = this.node.getBoundingClientRect();

				this.node.style.transform = "translateY(-"+prevRect.height+"px) translateZ(0)";
				prev.style.transform = "translateY("+currentRect.height+"px) translateZ(0)";

				setTimeout(function() {
					this.node.style.transform = null;
					prev.style.transform = null;
					this.node.style.transition = "none";
					prev.style.transition = "none";
					this.node.parentNode.insertBefore(this.node, prev);
					this.node.style.transition = null;
					prev.style.transition = null;
				}.bind(this), 200);

				BX.Landing.Main.getInstance().action("Landing::upBlock", {block: this.id});
			}
		},


		/**
		 * Moves block down
		 */
		moveDown: function()
		{
			var next = BX.findNextSibling(this.node, {className: "block-wrapper"});

			if (!!next)
			{
				var nextRect = next.getBoundingClientRect();
				var currentRect = this.node.getBoundingClientRect();

				this.node.style.transform = "translateY("+nextRect.height+"px) translateZ(0)";
				next.style.transform = "translateY(-"+currentRect.height+"px) translateZ(0)";

				setTimeout(function() {
					this.node.style.transform = null;
					next.style.transform = null;
					this.node.style.transition = "none";
					next.style.transition = "none";

					var nextNext = BX.findNextSibling(next, {className: "block-wrapper"});

					if (nextNext)
					{
						this.node.parentNode.insertBefore(this.node, nextNext);
						BX.Landing.Main.getInstance().action("Landing::downBlock", {block: this.id});
					}
					else
					{
						this.node.parentNode.appendChild(this.node);
						BX.Landing.Main.getInstance().action("Landing::downBlock", {block: this.id});
					}

					this.node.style.transition = null;
					next.style.transition = null;
				}.bind(this), 200);
			}
		},


		/**
		 * Adds panel into this block
		 * @param {BX.Landing.UI.Panel.BasePanel} panel
		 * @param {*} [target = this.node]
		 */
		addPanel: function(panel, target)
		{
			if (panel instanceof BX.Landing.UI.Panel.BasePanel && !this.panels.contains(panel))
			{
				this.panels.add(panel);
				if (!target)
				{
					this.node.appendChild(panel.layout);
				}
				else
				{
					target.parentNode.insertBefore(panel.layout, target);
				}
			}
		},


		/**
		 * Handles show panel event
		 * @private
		 */
		onShowContentPanel: function()
		{
			this.showContentPanel();
			BX.Landing.UI.Panel.EditorPanel.hide();
		},


		/**
		 * Handles state change event
		 * @private
		 */
		onStateChange: function()
		{
			if (this.isEnabled())
			{
				this.disable();
			}
			else
			{
				this.enable();
			}
		},


		/**
		 * Checks that block is enabled
		 * @return {boolean}
		 */
		isEnabled: function()
		{
			return this.active;
		},


		/**
		 * Enables block
		 */
		enable: function()
		{
			this.active = true;
			this.node.classList.remove("landing-block-disabled");
			// this.panels.get("block_action").buttons.get("show_hide").setText(BX.message("ACTION_BUTTON_HIDE"));
			BX.Landing.Main.getInstance().action("Landing::showBlock", {block: this.id});
		},


		/**
		 * Disables block
		 */
		disable: function()
		{
			this.active = false;
			this.node.classList.add("landing-block-disabled");
			// this.panels.get("block_action").buttons.get("show_hide").setText(BX.message("ACTION_BUTTON_SHOW"));
			BX.Landing.Main.getInstance().action("Landing::hideBlock", {block: this.id});
		},


		/**
		 * Init Cards of Block.
		 * @return {void}
		 */
		initCards: function()
		{
			this.forEachCard(function(node, selector, index) {

				var instance = this.cards.getByNode(node);
				var manifest = this.manifest.cards[selector];
				var cardSelector = selector + "@" + index;

				if (instance)
				{
					this.cards.remove(instance);
				}

				instance = new BX.Landing.Block.Card(node, manifest, cardSelector);
				this.cards.add(instance);

				if (!manifest.allowInlineEdit !== false)
				{
					var cardAction = new BX.Landing.UI.Panel.BaseButtonPanel(
						"cardAction",
						"landing-ui-panel-block-card-action"
					);
					cardAction.show();
					instance.addPanel(cardAction);

					cardAction.addButton(new BX.Landing.UI.Button.CardAction("clone", {
						text: "+",
						onClick: function() {
							this.cloneCard(cardSelector);
						}.bind(this)
					}));

					cardAction.addButton(new BX.Landing.UI.Button.CardAction("remove", {
						text: "-",
						onClick: function() {
							this.removeCard(cardSelector);
						}.bind(this)
					}));
				}

				instance.selector = cardSelector;
			});

			this.cards.sort(function(a, b) {
				return a.getIndex() > b.getIndex();
			});
		},


		/**
		 * Clones Card.
		 * @param {string} selector - Selector of Card, which want clone.
		 */
		cloneCard: function(selector)
		{
			var card = this.cards.getBySelector(selector);

			var beforeEvent = this.createEvent({card: card.node});
			BX.onCustomEvent("BX.Landing.Block:Card:beforeAdd", [beforeEvent]);

			var nodeCloned = BX.clone(card.node);
			BX.insertAfter(nodeCloned, card.node);
			this.initEntities();

			var cardNew = this.cards.getByNode(nodeCloned);
			var afterEvent = this.createEvent({card: cardNew.node});
			BX.onCustomEvent("BX.Landing.Block:Card:add", [afterEvent]);

			BX.Landing.Main.getInstance().action("Landing\\Block::cloneCard", {
				block: this.id,
				selector: selector
			});
		},


		/**
		 * Removes Card.
		 * @param {String} selector - Selector of Card, which want remove.
		 */
		removeCard: function(selector)
		{
			var card = this.cards.getBySelector(selector);

			var beforeEvent = this.createEvent({card: card.node});
			BX.onCustomEvent("BX.Landing.Block:Card:beforeRemove", [beforeEvent]);

			BX.remove(card.node);
			this.cards.remove(card);
			this.initEntities();

			// try to get new card with the same selector. If not exist - it was last card. Try to get previously card by index
			// we cant use just node navigation, because in slider nodes can be move to slides containers
			var cardNew = this.cards.getBySelector(selector);
			if (!cardNew)
			{
				var selectorName = selector.split("@")[0];
				var selectorIndex = parseInt(selector.split("@")[1]);
				if (!BX.type.isNumber(selectorIndex) || selectorIndex == 0)
					cardNew = null;
				else
					cardNew = this.cards.getBySelector(selectorName + "@" + (selectorIndex - 1));
			}
			var afterEvent = this.createEvent({card: (cardNew ? cardNew.node : null)});
			BX.onCustomEvent("BX.Landing.Block:Card:remove", [afterEvent]);

			BX.Landing.Main.getInstance().action("Landing\\Block::removeCard", {
				block: this.id,
				selector: selector
			});
		},


		/**
		 * @callback cardCallback
		 * @param {HTMLElement} node
		 * @param {string} selector
		 * @param {int} index
		 */
		/**
		 * Applies callback function for each card node
		 * @param {cardCallback} callback
		 */
		forEachCard: function(callback)
		{
			var cardSelectors = Object.keys(this.manifest.cards);

			cardSelectors.map(function(cardSelector) {
				var cards = [].slice.call(this.node.querySelectorAll(cardSelector));

				cards.forEach(function(node, index) {
					callback.apply(this, [node, cardSelector, index]);
				}, this);
			}, this);
		},


		/**
		 * Init Nodes of Block.
		 */
		initNodes: function()
		{
			this.forEachNodeElements(function(element, selector, index) {
				var instance = this.nodes.getByNode(element);
				var nodeSelector = selector + "@" + index;

				if (!instance)
				{
					var utils = new BX.Landing.Utils();
					var handler = utils.getClass(this.manifest.nodes[selector].handler);

					instance = new handler({
						node: element,
						manifest: this.manifest.nodes[selector],
						selector: nodeSelector,
						onChange: this.onNodeChange.bind(this)
					});

					this.nodes.add(instance);
				}

				instance.selector = nodeSelector;
			});

			this.nodes.sort(function(a, b) {
				return a.getIndex() > b.getIndex();
			});
		},


		/**
		 * @callback forEachNodeElementsCallback
		 * @param {HTMLElement} [element]
		 * @param {string} [selector]
		 * @param {int} [index]
		 */
		/**
		 * Applies callback for each element of node
		 * @param {forEachNodeElementsCallback} callback
		 */
		forEachNodeElements: function(callback)
		{
			Object.keys(this.manifest.nodes).forEach(function(selector) {
				[].slice.call(this.node.querySelectorAll(selector)).forEach(function(element, index) {
					callback.apply(this, [element, selector, index]);
				}, this);
			}, this);
		},


		/**
		 * Shows content edit panel
		 */
		showContentPanel: function()
		{
			var contentPanel = this.panels.get("content_edit");

			if (!contentPanel)
			{
				contentPanel = new BX.Landing.UI.Panel.ContentEdit("content_edit", {
					title: BX.message("LANDING_CONTENT_PANEL_TITLE"),
					footer: [
						new BX.Landing.UI.Button.BaseButton("cancel_block_content", {
							text: BX.message("BLOCK_CANCEL"),
							onClick: this.onContentCancel.bind(this),
							className: "landing-ui-button-content-cancel"
						}),
						new BX.Landing.UI.Button.BaseButton("save_block_content", {
							text: BX.message("BLOCK_SAVE"),
							onClick: this.onContentSave.bind(this),
							className: "landing-ui-button-content-save",
							disabled: true
						})
					]
				});

				contentPanel.layout.addEventListener("input", BX.debounce(this.onContentInput, 300, this));
				this.addPanel(contentPanel);
			}

			contentPanel.clear();
			contentPanel.buttons.get("save_block_content").disable();

			this.getEditForms().forEach(contentPanel.appendForm, contentPanel);

			contentPanel.show();
		},


		onStyleShow: function()
		{
			this.showStylePanel(this.selector);
			BX.Landing.UI.Panel.EditorPanel.hide();
		},


		/**
		 * Gets className postfix --lg --md --sm
		 */
		getPostfix: function()
		{
			return "";
		},


		/**
		 * Expands type groups
		 * @param {string|string[]} types
		 * @returns {string[]}
		 */
		expandTypeGroups: function(types)
		{
			var result = [];

			if (!BX.type.isArray(types))
			{
				types = [types];
			}

			types.forEach(function(type) {
				if (isGroup(type))
				{
					getGroupTypes(type).forEach(function(groupType) {
						result.push(groupType);
					});
				}
				else
				{
					result.push(type);
				}
			});

			return result;
		},


		/**
		 * Makes style editor form for style node
		 * @param {string} selector
		 * @param {{
		 * 		type: string|string[],
		 * 		name: string,
		 * 		[props],
		 * 		[title]
		 * 	}} settings
		 * @returns {?BX.Landing.UI.Form.StyleForm}
		 */
		makeStyleForm: function(selector, settings, isBlock)
		{
			var form = this.forms.get(selector);

			if (!form)
			{
				var type = !!settings.props ? settings.props : !!settings.type ? settings.type : null;
				var name = !!settings.title ? settings.title : !!settings.name ? settings.name : "";

				if (!!type && !!name)
				{
					var styleFactory = new BX.Landing.UI.Factory.StyleFactory({
						frame: window,
						postfix: this.getPostfix()
					});

					form = new BX.Landing.UI.Form.StyleForm({
						id: selector,
						title: name,
						selector: selector,
						iframe: window
					});

					type = this.expandTypeGroups(type);
					type.forEach(function(type) {
						var typeSettings = getTypeSettings(type);
						form.addField(styleFactory.createField({
							selector: !isBlock ? this.makeRelativeSelector(selector) : selector,
							property: type,
							type: typeSettings.type,
							title: typeSettings.name,
							items: typeSettings.items,
							onChange: function(value, items, postfix, affect) {
								this.styles.get(selector).setValue(value, items, postfix, affect);
								this.onStyleInputWithDebounce();
							}.bind(this)
						}));
					}, this);

					this.forms.add(form);
				}
			}

			return form;
		},


		initStyles: function()
		{
			this.styles.clear();
			var node = new BX.Landing.UI.Style({
				id: this.selector,
				iframe: window,
				selector: this.selector,
				relativeSelector: this.selector,
				onClick: this.onStyleClick.bind(this, this.selector)
			});

			this.styles.add(node);

			if (typeof this.manifest.style.nodes === "object")
			{
				Object.keys(this.manifest.style.nodes).forEach(function(selector) {
					var node = new BX.Landing.UI.Style({
						id: selector,
						iframe: window,
						selector: selector,
						relativeSelector: this.makeRelativeSelector(selector),
						onClick: this.onStyleClick.bind(this, selector)
					});

					this.styles.add(node);
				}, this);
			}
		},

		onStyleClick: function(selector)
		{
			this.showStylePanel(selector);
			var form = this.forms.get(selector);

			if (form)
			{
				BX.Landing.PageObject.getInstance().design().then(function(panel) {
					BX.Landing.UI.Panel.Content.scrollTo(panel.content, null);
				});
			}
		},


		/**
		 * Makes selector relative this block
		 * @param {string} selector
		 * @return {string}
		 */
		makeRelativeSelector: function(selector)
		{
			return this.selector + " " + selector;
		},


		saveStyles: function()
		{
			var styles = this.styles.fetchChanges();

			if (styles.length)
			{
				styles.forEach(function(style) {
					if (style.selector === this.selector)
					{
						style.selector = style.selector.replace(" > :first-child", "");
					}
				}, this);

				var post = styles.fetchValues();
				BX.Landing.Main.getInstance().action("Landing\\Block::updateStyles", {block: this.id, data: post});
			}
		},


		/**
		 * @todo Refactoring
		 */
		applyStyles: function()
		{
			this.styles.fetchChanges().forEach(function(styleNode) {
				var value = styleNode.getValue();
				var items = [];
				var block = document.querySelector(styleNode.selector);

				if (block)
				{
					items.push(block);
				}
				else
				{
					items = [].slice.call(this.node.querySelectorAll(styleNode.selector));
				}

				items.forEach(function(element) {
					element.className = value.classList.join(" ");

					var elementChild = [].slice.call(element.querySelectorAll("*"));
					value.affect.forEach(function(affectProperty) {
						element.style[affectProperty] = null;
						elementChild.forEach(function(child) {
							child.style[affectProperty] = null;
						});
					});
				});
			}, this);
		},


		/**
		 * Shows style editor panel
		 */
		showStylePanel: function(selector)
		{
			BX.Landing.PageObject.getInstance().design().then(function(stylePanel) {
				if (typeof this.manifest.style !== "object")
				{
					this.manifest.style = {};
				}

				stylePanel.content.innerHTML = "";

				var form;

				if (this.selector === selector)
				{
					var blockOptions = this.prepareBlockOptions(this.manifest.style.block);
					form = this.makeStyleForm(this.selector, blockOptions, true);
				}
				else
				{
					var settings = this.manifest.style.nodes[selector];
					form = this.makeStyleForm(selector, settings);
					stylePanel.appendForm(form);
				}

				stylePanel.appendForm(form);

				requestAnimationFrame(function() {
					stylePanel.show();
				});

			}.bind(this));
		},


		prepareBlockOptions: function(options)
		{
			if (typeof options !== "object")
			{
				options = {};
			}

			options.name = BX.message("BLOCK_STYLE_OPTIONS");

			if (typeof options.type !== "object" && typeof options.type !== "string")
			{
				options.type = [
					"display",
					"padding-top",
					"padding-bottom",
					"background-color",
					"background-gradient"
				];
			}

			return options;
		},


		/**
		 * Delete current block.
		 * @return {void}
		 */
		deleteBlock: function()
		{
			var event = this.createEvent();
			BX.onCustomEvent("BX.Landing.Block:remove", [event]);

			BX.remove(this.node);
			BX.Landing.Main.getInstance().action("Landing::deleteBlock", {block: this.id});
			BX.onCustomEvent("Landing.Block:onAfterDelete", [this]);

			BX.Landing.UI.Panel.EditorPanel.hide();
		},


		/**
		 * Shows blocks list panel
		 */
		addBlockAfterThis: function()
		{
			BX.Landing.Main.getInstance().showBlocksPanel(this);
		},


		/**
		 * Handles node content change event
		 * @param {BX.Landing.Block.Node} node
		 */
		onNodeChange: function(node)
		{
			var event = this.createEvent({node: node.node});
			BX.onCustomEvent("BX.Landing.Block:Node:update", [event]);

			if (!node.isSavePrevented())
			{
				clearTimeout(this.changeTimeout);
				this.changedNodes.add(node);

				this.changeTimeout = setTimeout(function() {
					BX.Landing.Main.getInstance().action(
						"Landing\\Block::updateNodes",
						{block: this.id, data: this.changedNodes.fetchValues()}
					);

					this.changedNodes.clear();
				}.bind(this), 100);
			}
		},


		/**
		 * Saves block content
		 */
		saveContent: function()
		{
			var panel = this.panels.get("content_edit");
			var post = {};
			var contentForms = new BX.Landing.UI.Collection.FormCollection();
			var attrForms = new BX.Landing.UI.Collection.FormCollection();

			panel.forms.forEach(function(form) {
				if (form.id === "attr")
				{
					attrForms.add(form);
				}
				else
				{
					contentForms.add(form);
				}
			});

			contentForms.fetchFields().fetchChanges().forEach(function(field) {
				post[field.selector] = field.getValue();
				var node = this.nodes.getBySelector(field.selector);
				var preventSave = true;
				node.setValue(field.getValue(), preventSave);
			}, this);

			attrForms.fetchFields().fetchChanges().forEach(function(field) {
				post[field.selector] = post[field.selector] || {};
				post[field.selector]["attrs"] = post[field.selector]["attrs"] || {};
				post[field.selector]["attrs"][field.property] = field.getValue();

				[].slice.call(this.node.querySelectorAll(field.selector)).forEach(function(node) {
					node.setAttribute(field.property, field.getValue());
				});
			}, this);

			BX.Landing.Main.getInstance().action("Landing\\Block::updateNodes", {block: this.id, data: post});
		},


		/**
		 * Handles content save event
		 */
		onContentSave: function()
		{
			this.saveContent();
			this.panels.get("content_edit").hide();
		},


		/**
		 * Handles content cancel edit event
		 */
		onContentCancel: function()
		{
			this.panels.get("content_edit").hide();
		},


		/**
		 * Handles content input event
		 */
		onContentInput: function()
		{
			var panel = this.panels.get("content_edit");

			if (panel.forms.fetchFields().isChanged())
			{
				panel.buttons.get("save_block_content").enable();
			}
			else
			{
				panel.buttons.get("save_block_content").disable();
			}
		},


		onStyleInput: function()
		{
			this.saveStyles();
		},


		/**
		 * Gets collection of edit forms
		 * @return {BX.Landing.UI.Collection.FormCollection.<BX.Landing.UI.Form.BaseForm>}
		 */
		getEditForms: function()
		{
			var forms = new BX.Landing.UI.Collection.FormCollection();
			// Make block form
			var blockNodes = this.nodes;

			if (this.cards.length && typeof this.manifest.cards === "object")
			{
				blockNodes = this.nodes.notMatches(
					Object.keys(this.manifest.cards).join(" *,") + " *, " + Object.keys(this.manifest.cards).join(",")
				);
			}

			if (blockNodes.length)
			{
				var blockForm = new BX.Landing.UI.Form.BaseForm({title: BX.message("BLOCK_HEADER")});
				blockNodes.forEach(function(node) {
					blockForm.addField(node.getField());
				});

				forms.add(blockForm);
			}

			if (typeof this.manifest.attrs === "object")
			{
				var keys = Object.keys(this.manifest.attrs);

				if (keys.length)
				{
					var attrsForm = new BX.Landing.UI.Form.BaseForm({id: "attr", title: BX.message("BLOCK_SETTINGS")});
					keys.forEach(function(selector) {
						var attr = this.manifest.attrs[selector];
						attrsForm.addField(
							new BX.Landing.UI.Field.Dropdown({
								title: attr.name,
								selector: this.makeRelativeSelector(selector),
								frame: window,
								property: attr.attribute,
								items: attr.items
							})
						);
					}, this);

					forms.add(attrsForm);
				}
			}

			// Makes card forms
			this.cards.forEach(function(card) {
				var cardForm = new BX.Landing.UI.Form.CardForm({title: card.getName()});

				this.nodes.forEach(function(node) {
					if (card.node.contains(node.node))
					{
						cardForm.addField(node.getField());
					}
				}, this);

				forms.add(cardForm);
			}, this);

			return forms;
		}
	};
})();